diff --git a/core/cmd/cmd.go b/core/cmd/cmd.go index ad291255d..d6c3f9d8f 100644 --- a/core/cmd/cmd.go +++ b/core/cmd/cmd.go @@ -434,7 +434,7 @@ func Init(ctx context.Context, flags *Flags, coreConfig *config.Config) { groupSvc := groupservice.NewService(manager) eventSvc := eventservice.New(manager) applicationSvc := applicationservice.NewService(groupSvc, manager) - clusterSvc := clusterservice.NewService(applicationSvc, manager) + clusterSvc := clusterservice.NewService(applicationSvc, clusterGitRepo, manager) userSvc := userservice.NewService(manager) tokenSvc := tokenservice.NewService(manager, coreConfig.TokenConfig) diff --git a/core/controller/application/controller_test.go b/core/controller/application/controller_test.go index eff244684..c55eda54d 100644 --- a/core/controller/application/controller_test.go +++ b/core/controller/application/controller_test.go @@ -28,6 +28,7 @@ import ( trschemamock "github.com/horizoncd/horizon/mock/pkg/templaterelease/schema" "github.com/horizoncd/horizon/pkg/application/gitrepo" "github.com/horizoncd/horizon/pkg/application/models" + appregionmodels "github.com/horizoncd/horizon/pkg/applicationregion/models" userauth "github.com/horizoncd/horizon/pkg/authentication/user" codemodels "github.com/horizoncd/horizon/pkg/cluster/code" clustermodels "github.com/horizoncd/horizon/pkg/cluster/models" @@ -36,6 +37,7 @@ import ( groupmodels "github.com/horizoncd/horizon/pkg/group/models" groupservice "github.com/horizoncd/horizon/pkg/group/service" membermodels "github.com/horizoncd/horizon/pkg/member/models" + "github.com/horizoncd/horizon/pkg/param" "github.com/horizoncd/horizon/pkg/param/managerparam" regionmodels "github.com/horizoncd/horizon/pkg/region/models" tagmodels "github.com/horizoncd/horizon/pkg/tag/models" @@ -298,6 +300,9 @@ func TestMain(m *testing.M) { if err := db.AutoMigrate(&usermodel.User{}); err != nil { panic(err) } + if err := db.AutoMigrate(&appregionmodels.ApplicationRegion{}); err != nil { + panic(err) + } ctx = context.TODO() ctx = context.WithValue(ctx, common.UserContextKey(), &userauth.DefaultInfo{ Name: "Tony", @@ -357,19 +362,15 @@ func Test(t *testing.T) { _, err := manager.TemplateReleaseMgr.Create(ctx, tr) assert.Nil(t, err) - c = &controller{ - applicationGitRepo: applicationGitRepo, - templateSchemaGetter: templateSchemaGetter, - tagMgr: manager.TagMgr, - applicationMgr: manager.ApplicationMgr, - groupMgr: manager.GroupMgr, - groupSvc: groupservice.NewService(manager), - templateReleaseMgr: manager.TemplateReleaseMgr, - clusterMgr: manager.ClusterMgr, - userSvc: userservice.NewService(manager), - eventSvc: eventservice.New(manager), - memberManager: manager.MemberMgr, + params := ¶m.Param{ + Manager: manager, + UserSvc: userservice.NewService(manager), + EventSvc: eventservice.New(manager), + GroupSvc: groupservice.NewService(manager), + ApplicationGitRepo: applicationGitRepo, + TemplateSchemaGetter: templateSchemaGetter, } + c = NewController(params) group, err := manager.GroupMgr.Create(ctx, &groupmodels.Group{ Name: "ABC", @@ -377,6 +378,12 @@ func Test(t *testing.T) { }) assert.Nil(t, err) + groupToTransfer, err := manager.GroupMgr.Create(ctx, &groupmodels.Group{ + Name: "ABC-transfer", + Path: "abc/transfer", + }) + assert.Nil(t, err) + createRequest := &CreateApplicationRequest{ Base: Base{ Description: "this is a description", @@ -445,6 +452,12 @@ func Test(t *testing.T) { assert.Equal(t, resp.Description, updatedDescription) + err = c.Transfer(ctx, resp.ID, groupToTransfer.ID) + assert.Nil(t, err) + resp, err = c.GetApplication(ctx, resp.ID) + assert.Nil(t, err) + assert.Equal(t, resp.GroupID, groupToTransfer.ID) + err = c.DeleteApplication(ctx, resp.ID, false) assert.Nil(t, err) @@ -513,20 +526,15 @@ func TestV2(t *testing.T) { } _, err := manager.TemplateReleaseMgr.Create(ctx, tr) assert.Nil(t, err) - c := &controller{ - applicationGitRepo: applicationGitRepo, - templateSchemaGetter: templateSchemaGetter, - applicationMgr: manager.ApplicationMgr, - tagMgr: manager.TagMgr, - groupMgr: manager.GroupMgr, - groupSvc: groupservice.NewService(manager), - templateReleaseMgr: manager.TemplateReleaseMgr, - clusterMgr: manager.ClusterMgr, - userSvc: userservice.NewService(manager), - eventSvc: eventservice.New(manager), - memberManager: manager.MemberMgr, + params := ¶m.Param{ + Manager: manager, + UserSvc: userservice.NewService(manager), + EventSvc: eventservice.New(manager), + GroupSvc: groupservice.NewService(manager), + ApplicationGitRepo: applicationGitRepo, + TemplateSchemaGetter: templateSchemaGetter, } - + c := NewController(params) group, err := manager.GroupMgr.Create(ctx, &groupmodels.Group{ Name: "cde", Path: "cde", @@ -673,12 +681,10 @@ func TestListUserApplication(t *testing.T) { applications = append(applications, application) } - c = &controller{ - applicationMgr: manager.ApplicationMgr, - groupMgr: manager.GroupMgr, - groupSvc: groupservice.NewService(manager), - memberManager: manager.MemberMgr, - } + c = NewController(¶m.Param{ + Manager: manager, + GroupSvc: groupservice.NewService(manager), + }) // nolint ctx = context.WithValue(ctx, common.UserContextKey(), &userauth.DefaultInfo{ diff --git a/core/controller/cluster/controller.go b/core/controller/cluster/controller.go index 6de7c35c9..a3a8bd52b 100644 --- a/core/controller/cluster/controller.go +++ b/core/controller/cluster/controller.go @@ -17,6 +17,7 @@ package cluster import ( "context" + clusterservice "github.com/horizoncd/horizon/pkg/cluster/service" templatemanager "github.com/horizoncd/horizon/pkg/template/manager" "k8s.io/apimachinery/pkg/runtime/schema" @@ -164,6 +165,7 @@ type controller struct { tokenConfig token.Config templateUpgradeMapper template.UpgradeMapper collectionManager collectionmanager.Manager + clusterSvc clusterservice.Service } var _ Controller = (*controller)(nil) @@ -206,5 +208,6 @@ func NewController(config *config.Config, param *param.Param) Controller { tokenConfig: config.TokenConfig, templateUpgradeMapper: config.TemplateUpgradeMapper, collectionManager: param.CollectionMgr, + clusterSvc: param.ClusterSvc, } } diff --git a/core/controller/cluster/controller_basic_v2.go b/core/controller/cluster/controller_basic_v2.go index 2563845f1..377818774 100644 --- a/core/controller/cluster/controller_basic_v2.go +++ b/core/controller/cluster/controller_basic_v2.go @@ -657,7 +657,7 @@ func (c *controller) CreatePipelineRun(ctx context.Context, clusterID uint, return nil, err } - // 找一下是否需要check,如果不需要则直接设为ready + // if checks is empty, set status to ready checks, err := c.prSvc.GetCheckByResource(ctx, clusterID, common.ResourceCluster) if err != nil { return nil, err @@ -683,19 +683,22 @@ func (c *controller) CreatePipelineRun(ctx context.Context, clusterID uint, func (c *controller) createPipelineRun(ctx context.Context, clusterID uint, r *CreatePipelineRunRequest) (*prmodels.Pipelinerun, error) { defer wlog.Start(ctx, "cluster controller: create pipeline run").StopPrint() - var action string - var err error cluster, err := c.clusterMgr.GetByID(ctx, clusterID) if err != nil { return nil, err } - if r.Action == prmodels.ActionBuildDeploy && cluster.GitURL == "" { - return nil, herrors.ErrBuildDeployNotSupported - } - var gitURL, gitRef, gitRefType, imageURL, codeCommitID = cluster.GitURL, - cluster.GitRef, cluster.GitRefType, cluster.Image, cluster.GitRef + var ( + title = r.Title + action string + gitURL = cluster.GitURL + gitRefType = cluster.GitRefType + gitRef = cluster.GitRef + codeCommitID string + imageURL = cluster.Image + rollbackFrom *uint + ) application, err := c.applicationMgr.GetByID(ctx, cluster.ApplicationID) if err != nil { @@ -717,6 +720,9 @@ func (c *controller) createPipelineRun(ctx context.Context, clusterID uint, switch r.Action { case prmodels.ActionBuildDeploy: action = prmodels.ActionBuildDeploy + if cluster.GitURL == "" { + return nil, herrors.ErrBuildDeployNotSupported + } if r.Git != nil { if r.Git.Commit != "" { @@ -768,6 +774,7 @@ func (c *controller) createPipelineRun(ctx context.Context, clusterID uint, } case prmodels.ActionRollback: + title = prmodels.ActionRollback action = prmodels.ActionRollback // get pipelinerun to rollback, and do some validation @@ -787,14 +794,20 @@ func (c *controller) createPipelineRun(ctx context.Context, clusterID uint, "the pipelinerun with id: %v is not belongs to cluster: %v", r.PipelinerunID, clusterID) } - // Deprecated: for internal usage - err = c.checkAndSyncGitOpsBranch(ctx, application.Name, cluster.Name, pipelinerun.ConfigCommit) - if err != nil { - return nil, err - } + gitURL = pipelinerun.GitURL + gitRefType = pipelinerun.GitRefType + gitRef = pipelinerun.GitRef + codeCommitID = pipelinerun.GitCommit + imageURL = pipelinerun.ImageURL + rollbackFrom = &pipelinerun.ID + configCommitSHA = configCommit.Master - gitURL, gitRefType, gitRef, codeCommitID, imageURL = - cluster.GitURL, cluster.GitRefType, cluster.GitRef, pipelinerun.GitCommit, pipelinerun.ImageURL + case prmodels.ActionRestart: + title = prmodels.ActionRestart + action = prmodels.ActionRestart + if cluster.Status == common.ClusterStatusFreed { + return nil, herrors.ErrFreedClusterNotSupportedRestart + } configCommitSHA = configCommit.Master default: @@ -805,7 +818,7 @@ func (c *controller) createPipelineRun(ctx context.Context, clusterID uint, ClusterID: clusterID, Action: action, Status: string(prmodels.StatusPending), - Title: r.Title, + Title: title, Description: r.Description, GitURL: gitURL, GitRefType: gitRefType, @@ -814,5 +827,6 @@ func (c *controller) createPipelineRun(ctx context.Context, clusterID uint, ImageURL: imageURL, LastConfigCommit: lastConfigCommitSHA, ConfigCommit: configCommitSHA, + RollbackFrom: rollbackFrom, }, nil } diff --git a/core/controller/cluster/controller_internal_v2.go b/core/controller/cluster/controller_internal_v2.go index e57c4c9ed..f26ac105b 100644 --- a/core/controller/cluster/controller_internal_v2.go +++ b/core/controller/cluster/controller_internal_v2.go @@ -93,6 +93,17 @@ func (c *controller) InternalDeployV2(ctx context.Context, clusterID uint, return nil } + // get original config commit, and sync pipeline's last_config_commit in DB + originalConfigCommit, err := c.clusterGitRepo.GetConfigCommit(ctx, application.Name, cluster.Name) + if err != nil { + return nil, err + } + if err := c.prMgr.PipelineRun.UpdateColumns(ctx, pr.ID, map[string]interface{}{ + "last_config_commit": originalConfigCommit.Master, + }); err != nil { + return nil, err + } + // 3. update pipeline output in git repo if builddeploy for gitImport and deploy for imageDeploy if (pr.Action == prmodels.ActionBuildDeploy && pr.GitURL != "") || (pr.Action == prmodels.ActionDeploy && pr.GitURL == "") { @@ -102,20 +113,21 @@ func (c *controller) InternalDeployV2(ctx context.Context, clusterID uint, if err != nil { return nil, perror.WithMessage(err, op) } - // 4. update config commit and status - if err := c.prMgr.PipelineRun.UpdateConfigCommitByID(ctx, pr.ID, commit); err != nil { - return nil, err - } if err := updatePRStatus(prmodels.StatusCommitted, commit); err != nil { return nil, err } } - // 5. merge branch from gitops to master if diff is not empty and update status + // 4. update config commit configCommit, err := c.clusterGitRepo.GetConfigCommit(ctx, application.Name, cluster.Name) if err != nil { return nil, err } + if err := c.prMgr.PipelineRun.UpdateConfigCommitByID(ctx, pr.ID, configCommit.Gitops); err != nil { + return nil, err + } + + // 5. merge branch from gitops to master if diff is not empty and update status diff, err := c.clusterGitRepo.CompareConfig(ctx, application.Name, cluster.Name, &configCommit.Master, &configCommit.Gitops) if err != nil { diff --git a/core/controller/cluster/controller_operation.go b/core/controller/cluster/controller_operation.go index c817d2b8e..924e69510 100644 --- a/core/controller/cluster/controller_operation.go +++ b/core/controller/cluster/controller_operation.go @@ -19,7 +19,6 @@ import ( "encoding/json" "fmt" "strconv" - "time" "github.com/google/go-containerregistry/pkg/name" "k8s.io/apimachinery/pkg/runtime/schema" @@ -36,7 +35,6 @@ import ( eventmodels "github.com/horizoncd/horizon/pkg/event/models" prmodels "github.com/horizoncd/horizon/pkg/pr/models" regionmodels "github.com/horizoncd/horizon/pkg/region/models" - tmodels "github.com/horizoncd/horizon/pkg/tag/models" trmodels "github.com/horizoncd/horizon/pkg/templaterelease/models" tokensvc "github.com/horizoncd/horizon/pkg/token/service" "github.com/horizoncd/horizon/pkg/util/log" @@ -57,6 +55,11 @@ func (c *controller) Restart(ctx context.Context, clusterID uint) (_ *Pipelineru return nil, err } + // freed cluster can not be restarted + if cluster.Status == common.ClusterStatusFreed { + return nil, herrors.ErrFreedClusterNotSupportedRestart + } + // 1. get config commit now lastConfigCommit, err := c.clusterGitRepo.GetConfigCommit(ctx, application.Name, cluster.Name) if err != nil { @@ -280,7 +283,7 @@ func getDeployImage(imageURL, deployTag string) (string, error) { func (c *controller) Rollback(ctx context.Context, clusterID uint, r *RollbackRequest) (_ *PipelinerunIDResponse, err error) { - const op = "cluster controller: rollback " + const op = "cluster controller: rollback" defer wlog.Start(ctx, op).StopPrint() // 1. get pipelinerun to rollback, and do some validation @@ -335,8 +338,8 @@ func (c *controller) Rollback(ctx context.Context, return nil, err } - // Deprecated: for internal usage - err = c.checkAndSyncGitOpsBranch(ctx, application.Name, cluster.Name, pipelinerun.ConfigCommit) + // for internal usage + err = c.clusterGitRepo.CheckAndSyncGitOpsBranch(ctx, application.Name, cluster.Name, pipelinerun.ConfigCommit) if err != nil { return nil, err } @@ -346,6 +349,11 @@ func (c *controller) Rollback(ctx context.Context, if err != nil { return nil, err } + if err := c.prMgr.PipelineRun.UpdateConfigCommitByID(ctx, prCreated.ID, newConfigCommit); err != nil { + log.Errorf(ctx, "UpdateConfigCommitByID error, pr = %d, commit = %s, err = %v", + prCreated.ID, newConfigCommit, err) + return nil, err + } if err := c.updatePipelineRunStatus(ctx, prmodels.ActionRollback, prCreated.ID, prmodels.StatusCommitted, newConfigCommit); err != nil { return nil, err @@ -357,10 +365,6 @@ func (c *controller) Rollback(ctx context.Context, if err != nil { return nil, err } - if err := c.prMgr.PipelineRun.UpdateConfigCommitByID(ctx, prCreated.ID, masterRevision); err != nil { - log.Errorf(ctx, "UpdateConfigCommitByID error, pr = %d, commit = %s, err = %v", - prCreated.ID, masterRevision, err) - } if err := c.updatePipelineRunStatus(ctx, prmodels.ActionRollback, prCreated.ID, prmodels.StatusMerged, masterRevision); err != nil { return nil, err @@ -368,7 +372,7 @@ func (c *controller) Rollback(ctx context.Context, // 6. update template and tags in db // TODO(zhuxu): remove strong dependencies on db updates, just print an err log when updates fail - cluster, err = c.updateTemplateAndTagsFromFile(ctx, application, cluster) + cluster, err = c.clusterSvc.SyncDBWithGitRepo(ctx, application, cluster) if err != nil { return nil, err } @@ -671,7 +675,7 @@ func (c *controller) Upgrade(ctx context.Context, clusterID uint) error { } // 3. sync gitops branch if restarts occur - err = c.syncGitOpsBranch(ctx, application.Name, cluster.Name) + err = c.clusterGitRepo.SyncGitOpsBranch(ctx, application.Name, cluster.Name) if err != nil { return err } @@ -701,17 +705,7 @@ func (c *controller) Upgrade(ctx context.Context, clusterID uint) error { func (c *controller) updatePipelineRunStatus(ctx context.Context, action string, prID uint, pState prmodels.PipelineStatus, revision string) error { - var err error - if pState != prmodels.StatusOK { - err = c.prMgr.PipelineRun.UpdateStatusByID(ctx, prID, pState) - } else { - finishedAt := time.Now() - err = c.prMgr.PipelineRun.UpdateResultByID(ctx, prID, &prmodels.Result{ - Result: string(pState), - FinishedAt: &finishedAt, - }) - } - if err != nil { + if err := c.prMgr.PipelineRun.UpdateStatusByID(ctx, prID, pState); err != nil { log.Errorf(ctx, "UpdateStatusByID error, pr = %d, status = %s, err = %v", prID, pState, err) return err @@ -720,124 +714,3 @@ func (c *controller) updatePipelineRunStatus(ctx context.Context, action, prID, pState, revision) return nil } - -// updateTemplateAndTagsFromFile syncs template and tags in db when git repo files are updated -func (c *controller) updateTemplateAndTagsFromFile(ctx context.Context, - application *amodels.Application, cluster *cmodels.Cluster) (*cmodels.Cluster, error) { - templateFromFile, err := c.clusterGitRepo.GetClusterTemplate(ctx, application.Name, cluster.Name) - if err != nil { - return nil, err - } - cluster.Template = templateFromFile.Name - cluster.TemplateRelease = templateFromFile.Release - cluster, err = c.clusterMgr.UpdateByID(ctx, cluster.ID, cluster) - if err != nil { - return nil, err - } - - files, err := c.clusterGitRepo.GetClusterValueFiles(ctx, application.Name, cluster.Name) - if err != nil { - return nil, err - } - - for _, file := range files { - if file.FileName == common.GitopsFileTags { - release, err := c.templateReleaseMgr.GetByTemplateNameAndRelease(ctx, cluster.Template, cluster.TemplateRelease) - if err != nil { - return nil, err - } - midMap := file.Content[release.ChartName].(map[string]interface{}) - tagsMap := midMap[common.GitopsKeyTags].(map[string]interface{}) - tags := make([]*tmodels.TagBasic, 0, len(tagsMap)) - for k, v := range tagsMap { - value, ok := v.(string) - if !ok { - continue - } - tags = append(tags, &tmodels.TagBasic{ - Key: k, - Value: value, - }) - } - return cluster, c.tagMgr.UpsertByResourceTypeID(ctx, - common.ResourceCluster, cluster.ID, tags) - } - } - return cluster, nil -} - -func (c *controller) checkAndSyncGitOpsBranch(ctx context.Context, application, - cluster string, commit string) error { - changed, err := c.manifestVersionChanged(ctx, application, cluster, commit) - if err != nil { - return err - } - if changed { - err = c.syncGitOpsBranch(ctx, application, cluster) - if err != nil { - return err - } - } - return nil -} - -// Deprecated: for internal usage -// manifestVersionChanged determines whether manifest version is changed -func (c *controller) manifestVersionChanged(ctx context.Context, application, - cluster string, commit string) (bool, error) { - currentManifest, err1 := c.clusterGitRepo.GetManifest(ctx, application, cluster, nil) - if err1 != nil { - if _, ok := perror.Cause(err1).(*herrors.HorizonErrNotFound); !ok { - log.Errorf(ctx, "get cluster manifest error, err = %s", err1.Error()) - return false, err1 - } - } - targetManifest, err2 := c.clusterGitRepo.GetManifest(ctx, application, cluster, &commit) - if err2 != nil { - if _, ok := perror.Cause(err2).(*herrors.HorizonErrNotFound); !ok { - log.Errorf(ctx, "get cluster manifest error, err = %s", err2.Error()) - return false, err2 - } - } - if err1 != nil && err2 != nil { - // manifest does not exist in both revisions - return false, nil - } - if err1 != nil || err2 != nil { - // One exists and the other does not exist in two revisions - return true, nil - } - return currentManifest.Version != targetManifest.Version, nil -} - -// Deprecated: for internal usage -// syncGitOpsBranch syncs gitOps branch with default branch to avoid merge conflicts. -// Restart updates time in restart.yaml in default branch. When other actions update -// template prefix in gitOps branch, there are merge conflicts in restart.yaml because -// usual context lines of 'git diff' are three. Ref: https://git-scm.com/docs/git-diff -// For example: -// -// <<<<<<< HEAD -// javaapp: -// restartTime: "2025-02-19 10:24:52" -// ======= -// rollout: -// restartTime: "2025-02-14 12:12:07" -// >>>>>>> gitops -func (c *controller) syncGitOpsBranch(ctx context.Context, application, cluster string) error { - gitOpsBranch := gitrepo.GitOpsBranch - defaultBranch := c.clusterGitRepo.DefaultBranch() - diff, err := c.clusterGitRepo.CompareConfig(ctx, application, cluster, - &gitOpsBranch, &defaultBranch) - if err != nil { - return err - } - if diff != "" { - _, err = c.clusterGitRepo.MergeBranch(ctx, application, - cluster, defaultBranch, gitOpsBranch, nil) - if err != nil { - return err - } - } - return nil -} diff --git a/core/controller/cluster/controller_test.go b/core/controller/cluster/controller_test.go index 6f9cf69b3..fb43aad05 100644 --- a/core/controller/cluster/controller_test.go +++ b/core/controller/cluster/controller_test.go @@ -25,6 +25,7 @@ import ( "time" tektoncollectormock "github.com/horizoncd/horizon/mock/pkg/cluster/tekton/collector" + appservice "github.com/horizoncd/horizon/pkg/application/service" clustercd "github.com/horizoncd/horizon/pkg/cd" eventservice "github.com/horizoncd/horizon/pkg/event/service" templatemodels "github.com/horizoncd/horizon/pkg/template/models" @@ -56,6 +57,7 @@ import ( codemodels "github.com/horizoncd/horizon/pkg/cluster/code" "github.com/horizoncd/horizon/pkg/cluster/gitrepo" "github.com/horizoncd/horizon/pkg/cluster/models" + cluterservice "github.com/horizoncd/horizon/pkg/cluster/service" gitconfig "github.com/horizoncd/horizon/pkg/config/git" templateconfig "github.com/horizoncd/horizon/pkg/config/template" tokenconfig "github.com/horizoncd/horizon/pkg/config/token" @@ -515,11 +517,11 @@ func TestAll(t *testing.T) { func test(t *testing.T) { // for test conf := config.Config{} - param := param.Param{ + parameter := param.Param{ AutoFreeSvc: service.New([]string{"test", "dev"}), Manager: managerparam.InitManager(nil), } - NewController(&conf, ¶m) + NewController(&conf, ¶meter) templateName := "javaapp" mockCtl := gomock.NewController(t) @@ -564,6 +566,9 @@ func test(t *testing.T) { registryDAO := registrydao.NewDAO(db) envRegionMgr := manager.EnvRegionMgr + groupSvc := groupservice.NewService(manager) + appSvc := appservice.NewService(groupSvc, manager) + // init data group, err := groupMgr.Create(ctx, &groupmodels.Group{ Name: "group", @@ -634,6 +639,7 @@ func test(t *testing.T) { c = &controller{ clusterMgr: manager.ClusterMgr, clusterGitRepo: clusterGitRepo, + clusterSvc: cluterservice.NewService(appSvc, clusterGitRepo, manager), commitGetter: commitGetter, cd: cd, k8sutil: k8sutil, @@ -644,7 +650,7 @@ func test(t *testing.T) { envMgr: envMgr, envRegionMgr: envRegionMgr, regionMgr: regionMgr, - autoFreeSvc: param.AutoFreeSvc, + autoFreeSvc: parameter.AutoFreeSvc, groupSvc: groupservice.NewService(manager), prMgr: manager.PRMgr, tektonFty: tektonFty, @@ -1087,8 +1093,8 @@ func test(t *testing.T) { Name: resp.Template.Name, Release: resp.Template.Release, }, nil).AnyTimes() - clusterGitRepo.EXPECT().GetManifest(ctx, application.Name, resp.Name, gomock.Any()). - Return(nil, herrors.NewErrNotFound(herrors.GitlabResource, "")).Times(2) + clusterGitRepo.EXPECT().CheckAndSyncGitOpsBranch(ctx, gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil).AnyTimes() // update status to 'ok' err = manager.PRMgr.PipelineRun.UpdateResultByID(ctx, buildDeployResp.PipelinerunID, &prmodels.Result{ Result: string(prmodels.StatusOK), @@ -1281,11 +1287,11 @@ func test(t *testing.T) { func testV2(t *testing.T) { // for test conf := config.Config{} - param := param.Param{ + parameter := param.Param{ AutoFreeSvc: service.New([]string{"dev", "test2"}), Manager: managerparam.InitManager(nil), } - NewController(&conf, ¶m) + NewController(&conf, ¶meter) templateName := "rollout" templateVersion := "v1.0.0" mockCtl := gomock.NewController(t) @@ -1415,7 +1421,7 @@ func testV2(t *testing.T) { groupSvc: groupservice.NewService(manager), prMgr: manager.PRMgr, userManager: manager.UserMgr, - autoFreeSvc: param.AutoFreeSvc, + autoFreeSvc: parameter.AutoFreeSvc, userSvc: userservice.NewService(manager), schemaTagManager: manager.ClusterSchemaTagMgr, applicationGitRepo: applicationGitRepo, @@ -1586,8 +1592,6 @@ func testUpgrade(t *testing.T) { Path: "group-upgrade", ParentID: 0, }) - t.Logf("%+v", err) - t.Logf("%+v", group) assert.Nil(t, err) assert.NotNil(t, group) gitURL := "ssh://git.com" @@ -1704,9 +1708,10 @@ func testUpgrade(t *testing.T) { Release: resp.Template.Release, }, nil).AnyTimes() clusterGitRepo.EXPECT().UpgradeCluster(ctx, gomock.Any()).Return("", nil).Times(1) - clusterGitRepo.EXPECT().DefaultBranch().Return("master").AnyTimes() - clusterGitRepo.EXPECT().CompareConfig(ctx, gomock.Any(), gomock.Any(), - gomock.Any(), gomock.Any()).Return("", nil).Times(1) + // clusterGitRepo.EXPECT().DefaultBranch().Return("master").AnyTimes() + // clusterGitRepo.EXPECT().CompareConfig(ctx, gomock.Any(), gomock.Any(), + // gomock.Any(), gomock.Any()).Return("", nil).Times(1) + clusterGitRepo.EXPECT().SyncGitOpsBranch(ctx, gomock.Any(), gomock.Any()).Return(nil).Times(1) err = c.Upgrade(ctx, resp.ID) assert.Nil(t, err) diff --git a/core/controller/member/controller_test.go b/core/controller/member/controller_test.go index a4aa68561..98455ddff 100644 --- a/core/controller/member/controller_test.go +++ b/core/controller/member/controller_test.go @@ -95,7 +95,7 @@ func createContext(t *testing.T) { groupSvc = groupservice.NewService(manager) applicationSvc = applicationservice.NewService(groupSvc, manager) - clusterSvc = clusterservice.NewService(applicationSvc, manager) + clusterSvc = clusterservice.NewService(applicationSvc, nil, manager) eventSvc = eventservice.New(manager) } diff --git a/core/controller/pipelinerun/controller.go b/core/controller/pipelinerun/controller.go index 257da76d4..e7b39a8f1 100644 --- a/core/controller/pipelinerun/controller.go +++ b/core/controller/pipelinerun/controller.go @@ -19,17 +19,21 @@ import ( "fmt" "net/http" "strconv" - "time" "github.com/horizoncd/horizon/core/common" "github.com/horizoncd/horizon/core/config" herrors "github.com/horizoncd/horizon/core/errors" "github.com/horizoncd/horizon/lib/q" appmanager "github.com/horizoncd/horizon/pkg/application/manager" + appmodels "github.com/horizoncd/horizon/pkg/application/models" + "github.com/horizoncd/horizon/pkg/authentication/user" + "github.com/horizoncd/horizon/pkg/cd" "github.com/horizoncd/horizon/pkg/cluster/code" codemodels "github.com/horizoncd/horizon/pkg/cluster/code" "github.com/horizoncd/horizon/pkg/cluster/gitrepo" clustermanager "github.com/horizoncd/horizon/pkg/cluster/manager" + clustermodels "github.com/horizoncd/horizon/pkg/cluster/models" + clusterservice "github.com/horizoncd/horizon/pkg/cluster/service" "github.com/horizoncd/horizon/pkg/cluster/tekton" "github.com/horizoncd/horizon/pkg/cluster/tekton/collector" "github.com/horizoncd/horizon/pkg/cluster/tekton/factory" @@ -67,7 +71,6 @@ type Controller interface { GetCheckRunByID(ctx context.Context, checkRunID uint) (*prmodels.CheckRun, error) UpdateCheckRunByID(ctx context.Context, checkRunID uint, request *CreateOrUpdateCheckRunRequest) error - ListMessagesByPipelinerun(ctx context.Context, pipelinerunID uint, query *q.Query) (int, []*prmodels.PRMessage, error) // Execute runs a pipelineRun only if its state is ready. Execute(ctx context.Context, pipelinerunID uint, force bool) error // Cancel withdraws a pipelineRun only if its state is pending. @@ -96,6 +99,8 @@ type controller struct { clusterGitRepo gitrepo.ClusterGitRepo userMgr usermanager.Manager eventSvc eventservice.Service + cd cd.CD + clusterSvc clusterservice.Service } var _ Controller = (*controller)(nil) @@ -117,6 +122,8 @@ func NewController(config *config.Config, param *param.Param) Controller { userMgr: param.UserMgr, templateReleaseMgr: param.TemplateReleaseMgr, eventSvc: param.EventSvc, + cd: param.CD, + clusterSvc: param.ClusterSvc, } } @@ -358,14 +365,6 @@ func (c *controller) UpdateCheckRunByID(ctx context.Context, checkRunID uint, return c.updatePrStatusByCheckrunID(ctx, checkRunID) } -func (c *controller) ListMessagesByPipelinerun(ctx context.Context, - pipelinerunID uint, query *q.Query) (int, []*prmodels.PRMessage, error) { - const op = "pipelinerun controller: list pr message" - defer wlog.Start(ctx, op).StopPrint() - - return c.prMgr.Message.List(ctx, pipelinerunID, query) -} - func (c *controller) Execute(ctx context.Context, pipelinerunID uint, force bool) error { const op = "pipelinerun controller: execute pipelinerun" defer wlog.Start(ctx, op).StopPrint() @@ -385,7 +384,13 @@ func (c *controller) Execute(ctx context.Context, pipelinerunID uint, force bool } } - return c.execute(ctx, pr) + err = c.execute(ctx, pr) + if err != nil { + return err + } + c.eventSvc.CreateEventIgnoreError(ctx, common.ResourcePipelinerun, pipelinerunID, + eventmodels.PipelinerunExecuted, nil) + return nil } func (c *controller) execute(ctx context.Context, pr *prmodels.Pipelinerun) error { @@ -394,26 +399,38 @@ func (c *controller) execute(ctx context.Context, pr *prmodels.Pipelinerun) erro return err } - // 1. get cluster + // 0. get resources cluster, err := c.clusterMgr.GetByID(ctx, pr.ClusterID) if err != nil { return err } - - // 2. get application application, err := c.appMgr.GetByID(ctx, cluster.ApplicationID) if err != nil { return err } - // 3. generate a JWT token for tekton callback - token, err := c.tokenSvc.CreateJWTToken(strconv.Itoa(int(currentUser.GetID())), + switch pr.Action { + case prmodels.ActionBuildDeploy, prmodels.ActionDeploy: + return c.executeDeploy(ctx, application, cluster, pr, currentUser) + case prmodels.ActionRestart: + return c.executeRestart(ctx, application, cluster, pr) + case prmodels.ActionRollback: + return c.executeRollback(ctx, application, cluster, pr) + default: + return perror.Wrapf(herrors.ErrParamInvalid, "unsupported action %v", pr.Action) + } +} + +func (c *controller) executeDeploy(ctx context.Context, application *appmodels.Application, + cluster *clustermodels.Cluster, pr *prmodels.Pipelinerun, currentUser user.User) error { + // 1. generate a JWT token for tekton callback + callbackToken, err := c.tokenSvc.CreateJWTToken(strconv.Itoa(int(currentUser.GetID())), c.tokenConfig.CallbackTokenExpireIn, tokensvc.WithPipelinerunID(pr.ID)) if err != nil { return err } - // 4. create pipelinerun in k8s + // 2. create pipelinerun in k8s tektonClient, err := c.tektonFty.GetTekton(cluster.EnvironmentName) if err != nil { return err @@ -465,7 +482,7 @@ func (c *controller) execute(ctx context.Context, pr *prmodels.Pipelinerun) erro Region: cluster.RegionName, RegionID: regionEntity.ID, Template: cluster.Template, - Token: token, + Token: callbackToken, }) if err != nil { return err @@ -473,11 +490,7 @@ func (c *controller) execute(ctx context.Context, pr *prmodels.Pipelinerun) erro // update event id returned from tekton-trigger EventListener log.Infof(ctx, "received event id: %s from tekton-trigger EventListener, pipelinerunID: %d", ciEventID, pr.ID) - err = c.prMgr.PipelineRun.UpdateColumns(ctx, pr.ID, map[string]interface{}{ - "ci_event_id": ciEventID, - "status": prmodels.StatusRunning, - "started_at": time.Now(), - }) + err = c.prMgr.PipelineRun.UpdateStatusByID(ctx, pr.ID, prmodels.StatusRunning) if err != nil { return err } @@ -485,7 +498,160 @@ func (c *controller) execute(ctx context.Context, pr *prmodels.Pipelinerun) erro if err != nil { return err } + return nil +} + +func (c *controller) executeRestart(ctx context.Context, application *appmodels.Application, + cluster *clustermodels.Cluster, pr *prmodels.Pipelinerun) error { + // 1. update pr status to running + if err := c.prMgr.PipelineRun.UpdateStatusByID(ctx, pr.ID, prmodels.StatusRunning); err != nil { + return perror.Wrapf(err, "failed to update pr status, pr = %d, status = %s", + pr.ID, prmodels.StatusRunning) + } + // 2. update restartTime in git repo, then update pr status to merged + lastConfigCommit, err := c.clusterGitRepo.GetConfigCommit(ctx, application.Name, cluster.Name) + if err != nil { + return perror.Wrapf(err, "failed to get last config commit, cluster = %s", cluster.Name) + } + commit, err := c.clusterGitRepo.UpdateRestartTime(ctx, application.Name, cluster.Name, cluster.Template) + if err != nil { + return perror.Wrapf(err, "failed to update cluster restart time, cluster = %s", cluster.Name) + } + if err := c.prMgr.PipelineRun.UpdateColumns(ctx, pr.ID, map[string]interface{}{ + "status": prmodels.StatusMerged, + "last_config_commit": lastConfigCommit.Master, + "config_commit": commit, + }); err != nil { + return perror.Wrapf(err, "failed to update pr columns, pr = %d, status = %s, config_commit = %s", + pr.ID, prmodels.StatusMerged, commit) + } + // 3. deploy cluster in cd system + if err := c.cd.DeployCluster(ctx, &cd.DeployClusterParams{ + Environment: cluster.EnvironmentName, + Cluster: cluster.Name, + Revision: commit, + }); err != nil { + return perror.Wrapf(err, "failed to deploy cluster in CD, cluster = %s, revision = %s", + cluster.Name, commit) + } + // 4. update pr status to ok + if err := c.prMgr.PipelineRun.UpdateStatusByID(ctx, pr.ID, prmodels.StatusOK); err != nil { + return perror.Wrapf(err, "failed to update pr status, pr = %d, status = %s", + pr.ID, prmodels.StatusOK) + } + // 5. create event + c.eventSvc.CreateEventIgnoreError(ctx, common.ResourceCluster, cluster.ID, + eventmodels.ClusterRestarted, nil) + return nil +} + +func (c *controller) executeRollback(ctx context.Context, application *appmodels.Application, + cluster *clustermodels.Cluster, pr *prmodels.Pipelinerun) error { + // 1. get pipelinerun to rollback + if pr.RollbackFrom == nil { + return perror.Wrapf(herrors.ErrParamInvalid, "pipelinerun to rollback is empty") + } + prToRollback, err := c.prMgr.PipelineRun.GetByID(ctx, *pr.RollbackFrom) + if err != nil { + return perror.Wrapf(err, "failed to get pipelinerun to rollback, pr = %d", *pr.RollbackFrom) + } + + // 2. update pr status to running + if err := c.prMgr.PipelineRun.UpdateStatusByID(ctx, pr.ID, prmodels.StatusRunning); err != nil { + return perror.Wrapf(err, "failed to update pr status, pr = %d, status = %s", + pr.ID, prmodels.StatusRunning) + } + + // for internal usage + if err = c.clusterGitRepo.CheckAndSyncGitOpsBranch(ctx, application.Name, + cluster.Name, prToRollback.ConfigCommit); err != nil { + return perror.Wrapf(err, "failed to check and sync gitops branch, cluster = %s", cluster.Name) + } + + // 3. rollback cluster config in git repo and update status + lastConfigCommit, err := c.clusterGitRepo.GetConfigCommit(ctx, application.Name, cluster.Name) + if err != nil { + return perror.Wrapf(err, "failed to get last config commit, cluster = %s", cluster.Name) + } + newConfigCommit, err := c.clusterGitRepo.Rollback(ctx, application.Name, cluster.Name, + prToRollback.ConfigCommit) + if err != nil { + return perror.Wrapf(err, "failed to rollback cluster config, cluster = %s, commit = %s", + cluster.Name, prToRollback.ConfigCommit) + } + if err := c.prMgr.PipelineRun.UpdateColumns(ctx, pr.ID, map[string]interface{}{ + "status": prmodels.StatusCommitted, + "last_config_commit": lastConfigCommit.Master, + "config_commit": newConfigCommit, + }); err != nil { + return perror.Wrapf(err, "failed to update pr columns, pr = %d, status = %s", + pr.ID, prmodels.StatusCommitted) + } + + // 4. merge branch & update config commit and status + masterRevision, err := c.clusterGitRepo.MergeBranch(ctx, application.Name, cluster.Name, + gitrepo.GitOpsBranch, c.clusterGitRepo.DefaultBranch(), &pr.ID) + if err != nil { + return perror.Wrapf(err, "failed to merge branch, cluster = %s", cluster.Name) + } + if err := c.prMgr.PipelineRun.UpdateStatusByID(ctx, pr.ID, prmodels.StatusMerged); err != nil { + return perror.Wrapf(err, "failed to update pr columns, pr = %d, status = %s, config_commit = %s", + pr.ID, prmodels.StatusMerged, masterRevision) + } + + // 5. update template and tags in db + cluster, err = c.clusterSvc.SyncDBWithGitRepo(ctx, application, cluster) + if err != nil { + return perror.Wrapf(err, "failed to sync db with git repo") + } + + // 6. create cluster in cd system + regionEntity, err := c.regionMgr.GetRegionEntity(ctx, cluster.RegionName) + if err != nil { + return perror.Wrapf(err, "failed to get region entity, region = %s", cluster.RegionName) + } + envValue, err := c.clusterGitRepo.GetEnvValue(ctx, application.Name, cluster.Name, cluster.Template) + if err != nil { + return perror.Wrapf(err, "failed to get env value, cluster = %s", cluster.Name) + } + repoInfo := c.clusterGitRepo.GetRepoInfo(ctx, application.Name, cluster.Name) + if err := c.cd.CreateCluster(ctx, &cd.CreateClusterParams{ + Environment: cluster.EnvironmentName, + Cluster: cluster.Name, + GitRepoURL: repoInfo.GitRepoURL, + ValueFiles: repoInfo.ValueFiles, + RegionEntity: regionEntity, + Namespace: envValue.Namespace, + }); err != nil { + return perror.Wrapf(err, "failed to create cluster in CD, cluster = %s", cluster.Name) + } + + // 7. reset cluster status + if cluster.Status == common.ClusterStatusFreed { + cluster.Status = common.ClusterStatusEmpty + cluster, err = c.clusterMgr.UpdateByID(ctx, cluster.ID, cluster) + if err != nil { + return perror.Wrapf(err, "failed to update cluster status, cluster = %s", cluster.Name) + } + } + + // 8. deploy cluster in cd and update status + if err := c.cd.DeployCluster(ctx, &cd.DeployClusterParams{ + Environment: cluster.EnvironmentName, + Cluster: cluster.Name, + Revision: masterRevision, + }); err != nil { + return perror.Wrapf(err, "failed to deploy cluster in CD, cluster = %s, revision = %s", + cluster.Name, masterRevision) + } + if err := c.prMgr.PipelineRun.UpdateStatusByID(ctx, pr.ID, prmodels.StatusOK); err != nil { + return perror.Wrapf(err, "failed to update pr status, pr = %d, status = %s", + pr.ID, prmodels.StatusOK) + } + // 9. record event + c.eventSvc.CreateEventIgnoreError(ctx, common.ResourceCluster, cluster.ID, + eventmodels.ClusterRollbacked, nil) return nil } @@ -572,7 +738,7 @@ func (c *controller) ListPRMessages(ctx context.Context, return 0, nil, err } userIDs := make([]uint, 0, len(messages)) - m := make(map[uint]struct{}, 0) + m := make(map[uint]struct{}) for _, message := range messages { if _, ok := m[message.CreatedBy]; !ok { userIDs = append(userIDs, message.CreatedBy) @@ -592,8 +758,8 @@ func (c *controller) ListPRMessages(ctx context.Context, return 0, nil, err } userMap := make(map[uint]*usermodels.User, 0) - for _, user := range users { - userMap[user.ID] = user + for _, u := range users { + userMap[u.ID] = u } result := make([]*PrMessage, 0, len(messages)) for _, message := range messages { @@ -601,21 +767,21 @@ func (c *controller) ListPRMessages(ctx context.Context, Content: message.Content, CreatedAt: message.CreatedAt, } - if user, ok := userMap[message.CreatedBy]; ok { + if u, ok := userMap[message.CreatedBy]; ok { resultMsg.CreatedBy = User{ - ID: user.ID, - Name: user.FullName, + ID: u.ID, + Name: u.FullName, } - if user.UserType == usermodels.UserTypeRobot { + if u.UserType == usermodels.UserTypeRobot { resultMsg.CreatedBy.UserType = "bot" } } - if user, ok := userMap[message.UpdatedBy]; ok { + if u, ok := userMap[message.UpdatedBy]; ok { resultMsg.UpdatedBy = User{ - ID: user.ID, - Name: user.FullName, + ID: u.ID, + Name: u.FullName, } - if user.UserType == usermodels.UserTypeRobot { + if u.UserType == usermodels.UserTypeRobot { resultMsg.CreatedBy.UserType = "bot" } } diff --git a/core/controller/pipelinerun/controller_test.go b/core/controller/pipelinerun/controller_test.go index 1987bbbd9..606ea29bf 100644 --- a/core/controller/pipelinerun/controller_test.go +++ b/core/controller/pipelinerun/controller_test.go @@ -23,6 +23,12 @@ import ( "time" "github.com/golang/mock/gomock" + cdmock "github.com/horizoncd/horizon/mock/pkg/cd" + applicationservice "github.com/horizoncd/horizon/pkg/application/service" + clusterservice "github.com/horizoncd/horizon/pkg/cluster/service" + eventmodels "github.com/horizoncd/horizon/pkg/event/models" + eventservice "github.com/horizoncd/horizon/pkg/event/service" + groupservice "github.com/horizoncd/horizon/pkg/group/service" "github.com/stretchr/testify/assert" "github.com/horizoncd/horizon/core/common" @@ -399,10 +405,10 @@ func TestExecutePipelineRun(t *testing.T) { if err := db.AutoMigrate(&applicationmodel.Application{}, &clustermodel.Cluster{}, ®ionmodels.Region{}, &membermodels.Member{}, ®istrymodels.Registry{}, &prmodels.Pipelinerun{}, &groupmodels.Group{}, &prmodels.Check{}, - &usermodel.User{}, &trmodels.TemplateRelease{}); err != nil { + &usermodel.User{}, &trmodels.TemplateRelease{}, &eventmodels.Event{}); err != nil { panic(err) } - param := managerparam.InitManager(db) + mgr := managerparam.InitManager(db) ctx := context.Background() // nolint ctx = context.WithValue(ctx, common.UserContextKey(), &userauth.DefaultInfo{ @@ -419,50 +425,65 @@ func TestExecutePipelineRun(t *testing.T) { JwtSigningKey: "hello", CallbackTokenExpireIn: 24 * time.Hour, } - mockClusterGitRepo := clustergitrepomock.NewMockClusterGitRepo(mockCtl) + mockCD := cdmock.NewMockCD(mockCtl) + + groupSvc := groupservice.NewService(mgr) + eventSvc := eventservice.New(mgr) + applicationSvc := applicationservice.NewService(groupSvc, mgr) + clusterSvc := clusterservice.NewService(applicationSvc, mockClusterGitRepo, mgr) ctrl := controller{ - prMgr: param.PRMgr, - appMgr: param.ApplicationMgr, - clusterMgr: param.ClusterMgr, - envMgr: param.EnvMgr, - regionMgr: param.RegionMgr, + prMgr: mgr.PRMgr, + appMgr: mgr.ApplicationMgr, + clusterMgr: mgr.ClusterMgr, + envMgr: mgr.EnvMgr, + regionMgr: mgr.RegionMgr, tektonFty: mockFactory, - tokenSvc: tokenservice.NewService(param, tokenConfig), + tokenSvc: tokenservice.NewService(mgr, tokenConfig), tokenConfig: tokenConfig, clusterGitRepo: mockClusterGitRepo, - templateReleaseMgr: param.TemplateReleaseMgr, + templateReleaseMgr: mgr.TemplateReleaseMgr, + cd: mockCD, + clusterSvc: clusterSvc, + eventSvc: eventSvc, } - _, err := param.UserMgr.Create(ctx, &usermodel.User{ + _, err1 := mgr.EventMgr.CreateEvent(ctx, &eventmodels.Event{ + EventSummary: eventmodels.EventSummary{ + ResourceID: 1, + }, + }) + assert.NoError(t, err1) + + _, err := mgr.UserMgr.Create(ctx, &usermodel.User{ Name: "Tony", }) assert.NoError(t, err) - group, err := param.GroupMgr.Create(ctx, &groupmodels.Group{ + group, err := mgr.GroupMgr.Create(ctx, &groupmodels.Group{ Name: "test", }) assert.NoError(t, err) - app, err := param.ApplicationMgr.Create(ctx, &applicationmodel.Application{ + app, err := mgr.ApplicationMgr.Create(ctx, &applicationmodel.Application{ Name: "test", GroupID: group.ID, }, nil) assert.NoError(t, err) - registryID, err := param.RegistryMgr.Create(ctx, ®istrymodels.Registry{ + registryID, err := mgr.RegistryMgr.Create(ctx, ®istrymodels.Registry{ Name: "test", }) assert.NoError(t, err) - region, err := param.RegionMgr.Create(ctx, ®ionmodels.Region{ + region, err := mgr.RegionMgr.Create(ctx, ®ionmodels.Region{ Name: "test", RegistryID: registryID, }) assert.NoError(t, err) - cluster, err := param.ClusterMgr.Create(ctx, &clustermodel.Cluster{ + cluster, err := mgr.ClusterMgr.Create(ctx, &clustermodel.Cluster{ Name: "clusterGit", ApplicationID: app.ID, GitURL: "hello", @@ -472,7 +493,7 @@ func TestExecutePipelineRun(t *testing.T) { }, nil, nil) assert.NoError(t, err) - _, err = param.TemplateReleaseMgr.Create(ctx, &trmodels.TemplateRelease{ + _, err = mgr.TemplateReleaseMgr.Create(ctx, &trmodels.TemplateRelease{ TemplateName: "javaapp", Name: "v1.0.0", }) @@ -483,33 +504,102 @@ func TestExecutePipelineRun(t *testing.T) { PipelineJSONBlob: map[string]interface{}{}, ApplicationJSONBlob: map[string]interface{}{}, }, nil).AnyTimes() + mockClusterGitRepo.EXPECT().GetConfigCommit(gomock.Any(), gomock.Any(), gomock.Any()). + Return(&clustergitrepo.ClusterCommit{ + Master: "master", + Gitops: "gitops", + }, nil).AnyTimes() + mockClusterGitRepo.EXPECT().UpdateRestartTime(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return("restart_commit", nil).AnyTimes() + mockClusterGitRepo.EXPECT().CheckAndSyncGitOpsBranch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil).AnyTimes() + mockClusterGitRepo.EXPECT().Rollback(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return("rollback_commit", nil).AnyTimes() + mockClusterGitRepo.EXPECT().MergeBranch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), + gomock.Any(), gomock.Any()).Return("rollback_master_commit", nil).AnyTimes() + mockClusterGitRepo.EXPECT().GetClusterTemplate(gomock.Any(), gomock.Any(), gomock.Any()). + Return(&clustergitrepo.ClusterTemplate{ + Name: "javaapp", + Release: "v1.0.0", + }, nil).AnyTimes() + mockClusterGitRepo.EXPECT().GetClusterValueFiles(gomock.Any(), gomock.Any(), gomock.Any()). + Return([]clustergitrepo.ClusterValueFile{}, nil).AnyTimes() + mockClusterGitRepo.EXPECT().GetEnvValue(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(&clustergitrepo.EnvValue{ + Environment: "test", + Region: region.Name, + Namespace: "default", + BaseRegistry: "registry.cn-hangzhou.aliyuncs.com", + IngressDomain: region.IngressDomain, + }, nil).AnyTimes() + mockClusterGitRepo.EXPECT().GetRepoInfo(gomock.Any(), gomock.Any(), gomock.Any()). + Return(&clustergitrepo.RepoInfo{}).AnyTimes() + mockClusterGitRepo.EXPECT().DefaultBranch().Return("master").AnyTimes() mockTektonInterface.EXPECT().CreatePipelineRun(ctx, gomock.Any()).Return("hello", nil).AnyTimes() - PRPending, err := param.PRMgr.PipelineRun.Create(ctx, &prmodels.Pipelinerun{ + mockCD.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + mockCD.EXPECT().DeployCluster(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + + prPending, err := mgr.PRMgr.PipelineRun.Create(ctx, &prmodels.Pipelinerun{ ClusterID: cluster.ID, + Action: prmodels.ActionBuildDeploy, Status: string(pipelinemodel.StatusPending), }) assert.NoError(t, err) - assert.Equal(t, string(pipelinemodel.StatusPending), PRPending.Status) + assert.Equal(t, string(pipelinemodel.StatusPending), prPending.Status) - PRReady, err := param.PRMgr.PipelineRun.Create(ctx, &prmodels.Pipelinerun{ + prDeployReady, err := mgr.PRMgr.PipelineRun.Create(ctx, &prmodels.Pipelinerun{ ClusterID: cluster.ID, + Action: prmodels.ActionDeploy, Status: string(pipelinemodel.StatusReady), }) assert.NoError(t, err) - assert.Equal(t, string(pipelinemodel.StatusReady), PRReady.Status) + assert.Equal(t, string(pipelinemodel.StatusReady), prDeployReady.Status) + + prRestartReady, err := mgr.PRMgr.PipelineRun.Create(ctx, &prmodels.Pipelinerun{ + ClusterID: cluster.ID, + Action: prmodels.ActionRestart, + Status: string(pipelinemodel.StatusReady), + }) + assert.NoError(t, err) + assert.Equal(t, string(pipelinemodel.StatusReady), prRestartReady.Status) + + prOK, err := mgr.PRMgr.PipelineRun.Create(ctx, &prmodels.Pipelinerun{ + ClusterID: cluster.ID, + Action: prmodels.ActionBuildDeploy, + Status: string(pipelinemodel.StatusOK), + ConfigCommit: "ok_commit", + LastConfigCommit: "last_ok_commit", + }) + assert.NoError(t, err) + assert.Equal(t, string(pipelinemodel.StatusOK), prOK.Status) + + prRollbackReady, err := mgr.PRMgr.PipelineRun.Create(ctx, &prmodels.Pipelinerun{ + ClusterID: cluster.ID, + Action: prmodels.ActionRollback, + Status: string(pipelinemodel.StatusReady), + RollbackFrom: &prOK.ID, + }) + assert.NoError(t, err) + assert.Equal(t, string(pipelinemodel.StatusReady), prRollbackReady.Status) + + err = ctrl.Execute(ctx, prDeployReady.ID, false) + assert.NoError(t, err) + + err = ctrl.Execute(ctx, prRestartReady.ID, false) + assert.NoError(t, err) - err = ctrl.Execute(ctx, PRReady.ID, false) + err = ctrl.Execute(ctx, prRollbackReady.ID, false) assert.NoError(t, err) - err = ctrl.Execute(ctx, PRPending.ID, false) + err = ctrl.Execute(ctx, prPending.ID, false) assert.NotNil(t, err) - err = ctrl.Execute(ctx, PRPending.ID, true) + err = ctrl.Execute(ctx, prPending.ID, true) assert.NoError(t, err) - PRCancel, err := param.PRMgr.PipelineRun.Create(ctx, &prmodels.Pipelinerun{ + PRCancel, err := mgr.PRMgr.PipelineRun.Create(ctx, &prmodels.Pipelinerun{ ClusterID: cluster.ID, Status: string(pipelinemodel.StatusPending), }) @@ -518,7 +608,7 @@ func TestExecutePipelineRun(t *testing.T) { err = ctrl.Cancel(ctx, PRCancel.ID) assert.NoError(t, err) - err = ctrl.Cancel(ctx, PRReady.ID) + err = ctrl.Cancel(ctx, prDeployReady.ID) assert.NotNil(t, err) } diff --git a/core/controller/tag/controller_test.go b/core/controller/tag/controller_test.go index 93ef3f62e..af130e404 100644 --- a/core/controller/tag/controller_test.go +++ b/core/controller/tag/controller_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/golang/mock/gomock" + "github.com/horizoncd/horizon/pkg/param" "github.com/stretchr/testify/assert" "github.com/horizoncd/horizon/core/common" @@ -50,7 +51,7 @@ func TestMain(m *testing.M) { manager = managerparam.InitManager(db) if err := db.AutoMigrate(&appmodels.Application{}, &models.Cluster{}, &tagmodels.Tag{}, &membermodels.Member{}, ®ionmodels.Region{}, - &trmodels.TemplateRelease{}, &templatemodels.Template{}); err != nil { + &trmodels.TemplateRelease{}, &templatemodels.Template{}, &tagmodels.Metatag{}); err != nil { panic(err) } ctx = context.TODO() @@ -109,12 +110,10 @@ func Test(t *testing.T) { }) assert.Nil(t, err) - c = &controller{ - clusterMgr: clusterMgr, - tagMgr: manager.TagMgr, - clusterGitRepo: clusterGitRepo, - applicationMgr: appMgr, - } + c = NewController(¶m.Param{ + Manager: manager, + ClusterGitRepo: clusterGitRepo, + }) clusterID := cluster.ID err = c.Update(ctx, common.ResourceCluster, clusterID, &UpdateRequest{ @@ -249,4 +248,33 @@ func Test(t *testing.T) { resp, err = c.ListSubResourceTags(ctx, common.ResourceApplication, cluster.ApplicationID) assert.Nil(t, err) assert.Equal(t, 2, len(resp.Tags)) + + // test meta tag + err = c.CreateMetatags(ctx, &CreateMetatagsRequest{ + Metatags: []*tagmodels.Metatag{ + { + TagKey: "k1", + TagValue: "v1", + Description: "desc1", + }, + { + TagKey: "k1", + TagValue: "v2", + Description: "desc2", + }, + { + TagKey: "k2", + TagValue: "v3", + Description: "desc3", + }, + }, + }) + assert.Nil(t, err) + metatags, err := c.GetMetatagsByKey(ctx, "k1") + assert.Nil(t, err) + assert.Equal(t, 2, len(metatags)) + + metakeys, err := c.GetMetatagKeys(ctx) + assert.Nil(t, err) + assert.Equal(t, 2, len(metakeys)) } diff --git a/core/errors/horizonerrors.go b/core/errors/horizonerrors.go index 4eb795e96..391d1592b 100644 --- a/core/errors/horizonerrors.go +++ b/core/errors/horizonerrors.go @@ -219,9 +219,10 @@ var ( ErrHTTPRequestFailed = errors.New("http request failed") // cluster - ErrClusterNoChange = errors.New("no change to cluster") - ErrShouldBuildDeployFirst = errors.New("clusters with build config should build and deploy first") - ErrBuildDeployNotSupported = errors.New("builddeploy is not supported for this cluster") + ErrClusterNoChange = errors.New("no change to cluster") + ErrShouldBuildDeployFirst = errors.New("clusters with build config should build and deploy first") + ErrBuildDeployNotSupported = errors.New("builddeploy is not supported for this cluster") + ErrFreedClusterNotSupportedRestart = errors.New("freed cluster is not supported to restart") // pipelinerun diff --git a/core/http/api/v1/cluster/apis_operation.go b/core/http/api/v1/cluster/apis_operation.go index db57ca6e1..21d67c638 100644 --- a/core/http/api/v1/cluster/apis_operation.go +++ b/core/http/api/v1/cluster/apis_operation.go @@ -217,6 +217,10 @@ func (a *API) Restart(c *gin.Context) { resp, err := a.clusterCtl.Restart(c, uint(clusterID)) if err != nil { + if perror.Cause(err) == herrors.ErrFreedClusterNotSupportedRestart { + response.AbortWithRPCError(c, rpcerror.BadRequestError.WithErrMsg(err.Error())) + return + } if e, ok := perror.Cause(err).(*herrors.HorizonErrNotFound); ok && e.Source == herrors.ClusterInDB { response.AbortWithRPCError(c, rpcerror.NotFoundError.WithErrMsg(err.Error())) return diff --git a/core/http/api/v2/application/routers.go b/core/http/api/v2/application/routers.go index 0ddbe5679..d0f2fab8d 100644 --- a/core/http/api/v2/application/routers.go +++ b/core/http/api/v2/application/routers.go @@ -24,7 +24,7 @@ import ( "github.com/gin-gonic/gin" ) -// RegisterRoutes register routes +// RegisterRoute registers routes func (api *API) RegisterRoute(engine *gin.Engine) { apiV2Group := engine.Group("/apis/core/v2") apiV2Routes := route.Routes{ diff --git a/core/http/api/v2/cluster/apis_operation.go b/core/http/api/v2/cluster/apis_operation.go index 86f05898a..8ea9585ff 100644 --- a/core/http/api/v2/cluster/apis_operation.go +++ b/core/http/api/v2/cluster/apis_operation.go @@ -335,6 +335,10 @@ func (a *API) Restart(c *gin.Context) { resp, err := a.clusterCtl.Restart(c, uint(clusterID)) if err != nil { + if perror.Cause(err) == herrors.ErrFreedClusterNotSupportedRestart { + response.AbortWithRPCError(c, rpcerror.BadRequestError.WithErrMsg(err.Error())) + return + } if e, ok := perror.Cause(err).(*herrors.HorizonErrNotFound); ok && e.Source == herrors.ClusterInDB { response.AbortWithRPCError(c, rpcerror.NotFoundError.WithErrMsg(err.Error())) return diff --git a/mock/pkg/cluster/gitrepo/gitrepo_cluster_mock.go b/mock/pkg/cluster/gitrepo/gitrepo_cluster_mock.go index 6631bde7d..65d946f51 100644 --- a/mock/pkg/cluster/gitrepo/gitrepo_cluster_mock.go +++ b/mock/pkg/cluster/gitrepo/gitrepo_cluster_mock.go @@ -37,6 +37,20 @@ func (m *MockClusterGitRepo) EXPECT() *MockClusterGitRepoMockRecorder { return m.recorder } +// CheckAndSyncGitOpsBranch mocks base method. +func (m *MockClusterGitRepo) CheckAndSyncGitOpsBranch(ctx context.Context, application, cluster, commit string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckAndSyncGitOpsBranch", ctx, application, cluster, commit) + ret0, _ := ret[0].(error) + return ret0 +} + +// CheckAndSyncGitOpsBranch indicates an expected call of CheckAndSyncGitOpsBranch. +func (mr *MockClusterGitRepoMockRecorder) CheckAndSyncGitOpsBranch(ctx, application, cluster, commit interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckAndSyncGitOpsBranch", reflect.TypeOf((*MockClusterGitRepo)(nil).CheckAndSyncGitOpsBranch), ctx, application, cluster, commit) +} + // CompareConfig mocks base method. func (m *MockClusterGitRepo) CompareConfig(ctx context.Context, application, cluster string, from, to *string) (string, error) { m.ctrl.T.Helper() @@ -272,6 +286,20 @@ func (mr *MockClusterGitRepoMockRecorder) Rollback(ctx, application, cluster, co return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Rollback", reflect.TypeOf((*MockClusterGitRepo)(nil).Rollback), ctx, application, cluster, commit) } +// SyncGitOpsBranch mocks base method. +func (m *MockClusterGitRepo) SyncGitOpsBranch(ctx context.Context, application, cluster string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncGitOpsBranch", ctx, application, cluster) + ret0, _ := ret[0].(error) + return ret0 +} + +// SyncGitOpsBranch indicates an expected call of SyncGitOpsBranch. +func (mr *MockClusterGitRepoMockRecorder) SyncGitOpsBranch(ctx, application, cluster interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncGitOpsBranch", reflect.TypeOf((*MockClusterGitRepo)(nil).SyncGitOpsBranch), ctx, application, cluster) +} + // UpdateCluster mocks base method. func (m *MockClusterGitRepo) UpdateCluster(ctx context.Context, params *gitrepo.UpdateClusterParams) error { m.ctrl.T.Helper() diff --git a/pkg/application/service/service_test.go b/pkg/application/service/service_test.go index 3e6ebbe97..a30699c3d 100644 --- a/pkg/application/service/service_test.go +++ b/pkg/application/service/service_test.go @@ -31,9 +31,10 @@ import ( var ( // use tmp sqlite - db, _ = orm.NewSqliteDB("") - ctx = context.TODO() - mgr = managerparam.InitManager(db) + db, _ = orm.NewSqliteDB("") + ctx = context.TODO() + mgr = managerparam.InitManager(db) + groupSvc = groupservice.NewService(mgr) ) // nolint @@ -61,20 +62,14 @@ func TestServiceGetByID(t *testing.T) { db.Save(application) t.Run("GetByID", func(t *testing.T) { - s := service{ - groupSvc: groupservice.NewService(mgr), - appMgr: mgr.ApplicationMgr, - } + s := NewService(groupSvc, mgr) result, err := s.GetByID(ctx, application.ID) assert.Nil(t, err) assert.Equal(t, "/a/b", result.FullPath) }) t.Run("GetByIDs", func(t *testing.T) { - s := service{ - groupSvc: groupservice.NewService(mgr), - appMgr: mgr.ApplicationMgr, - } + s := NewService(groupSvc, mgr) result, err := s.GetByIDs(ctx, []uint{application.ID}) assert.Nil(t, err) assert.Equal(t, 1, len(result)) diff --git a/pkg/cluster/gitrepo/gitrepo_cluster.go b/pkg/cluster/gitrepo/gitrepo_cluster.go index d5bc86254..66b1df024 100644 --- a/pkg/cluster/gitrepo/gitrepo_cluster.go +++ b/pkg/cluster/gitrepo/gitrepo_cluster.go @@ -154,6 +154,11 @@ type ClusterGitRepo interface { // GetManifest returns manifest with specific revision, defaults to gitops branch GetManifest(ctx context.Context, application, cluster string, commit *string) (*pkgcommon.Manifest, error) + // CheckAndSyncGitOpsBranch checks and sync if gitops branch is not up-to-date with master branch + // for internal usage + CheckAndSyncGitOpsBranch(ctx context.Context, application, cluster, commit string) error + // SyncGitOpsBranch syncs gitops branch to up-to-date with master branch + SyncGitOpsBranch(ctx context.Context, application, cluster string) error } type clusterGitopsRepo struct { gitlabLib gitlablib.Interface @@ -1079,6 +1084,20 @@ func (g *clusterGitopsRepo) GetEnvValue(ctx context.Context, return envMap[templateName][common.GitopsEnvValueNamespace], nil } +func (g *clusterGitopsRepo) CheckAndSyncGitOpsBranch(ctx context.Context, application, cluster, commit string) error { + changed, err := g.manifestVersionChanged(ctx, application, cluster, commit) + if err != nil { + return err + } + if changed { + err = g.SyncGitOpsBranch(ctx, application, cluster) + if err != nil { + return err + } + } + return nil +} + func (g *clusterGitopsRepo) Rollback(ctx context.Context, application, cluster, commit string) (_ string, err error) { const op = "cluster git repo: rollback" defer wlog.Start(ctx, op).StopPrint() @@ -1759,6 +1778,66 @@ func (g *clusterGitopsRepo) readFile(ctx context.Context, application, cluster, return g.gitlabLib.GetFile(ctx, pid, GitOpsBranch, fileName) } +// for internal usage +// manifestVersionChanged determines whether manifest version is changed +func (g *clusterGitopsRepo) manifestVersionChanged(ctx context.Context, application, + cluster string, commit string) (bool, error) { + currentManifest, err1 := g.GetManifest(ctx, application, cluster, nil) + if err1 != nil { + if _, ok := perror.Cause(err1).(*herrors.HorizonErrNotFound); !ok { + log.Errorf(ctx, "get cluster manifest error, err = %s", err1.Error()) + return false, err1 + } + } + targetManifest, err2 := g.GetManifest(ctx, application, cluster, &commit) + if err2 != nil { + if _, ok := perror.Cause(err2).(*herrors.HorizonErrNotFound); !ok { + log.Errorf(ctx, "get cluster manifest error, err = %s", err2.Error()) + return false, err2 + } + } + if err1 != nil && err2 != nil { + // manifest does not exist in both revisions + return false, nil + } + if err1 != nil || err2 != nil { + // One exists and the other does not exist in two revisions + return true, nil + } + return currentManifest.Version != targetManifest.Version, nil +} + +// SyncGitOpsBranch for internal usage, syncs gitOps branch with default branch to avoid merge conflicts. +// Restart updates time in restart.yaml in default branch. When other actions update +// template prefix in gitOps branch, there are merge conflicts in restart.yaml because +// usual context lines of 'git diff' are three. Ref: https://git-scm.com/docs/git-diff +// For example: +// +// <<<<<<< HEAD +// javaapp: +// restartTime: "2025-02-19 10:24:52" +// ======= +// rollout: +// restartTime: "2025-02-14 12:12:07" +// >>>>>>> gitops +func (g *clusterGitopsRepo) SyncGitOpsBranch(ctx context.Context, application, cluster string) error { + gitOpsBranch := GitOpsBranch + defaultBranch := g.DefaultBranch() + diff, err := g.CompareConfig(ctx, application, cluster, + &gitOpsBranch, &defaultBranch) + if err != nil { + return err + } + if diff != "" { + _, err = g.MergeBranch(ctx, application, + cluster, defaultBranch, gitOpsBranch, nil) + if err != nil { + return err + } + } + return nil +} + func renameTemplateName(name string) string { templateName := []byte(name) for i := range templateName { diff --git a/pkg/cluster/service/service.go b/pkg/cluster/service/service.go index 2e7778afc..1620df6a7 100644 --- a/pkg/cluster/service/service.go +++ b/pkg/cluster/service/service.go @@ -18,28 +18,54 @@ import ( "context" "fmt" - applicationservice "github.com/horizoncd/horizon/pkg/application/service" + "github.com/horizoncd/horizon/core/common" + appmodels "github.com/horizoncd/horizon/pkg/application/models" + appservice "github.com/horizoncd/horizon/pkg/application/service" + "github.com/horizoncd/horizon/pkg/cluster/gitrepo" clustermanager "github.com/horizoncd/horizon/pkg/cluster/manager" + clustermodels "github.com/horizoncd/horizon/pkg/cluster/models" "github.com/horizoncd/horizon/pkg/param/managerparam" + tagmanager "github.com/horizoncd/horizon/pkg/tag/manager" + tmodels "github.com/horizoncd/horizon/pkg/tag/models" + trmanager "github.com/horizoncd/horizon/pkg/templaterelease/manager" ) type Service interface { // GetByID get detail of an application by id GetByID(ctx context.Context, id uint) (*ClusterDetail, error) + // SyncDBWithGitRepo syncs template and tags in db when git repo files are updated + SyncDBWithGitRepo(ctx context.Context, application *appmodels.Application, + cluster *clustermodels.Cluster) (*clustermodels.Cluster, error) } type service struct { - applicationService applicationservice.Service - clusterManager clustermanager.Manager + appSvc appservice.Service + clusterMgr clustermanager.Manager + trMgr trmanager.Manager + tagMgr tagmanager.Manager + clusterGitRepo gitrepo.ClusterGitRepo +} + +var _ Service = (*service)(nil) + +func NewService(applicationSvc appservice.Service, clusterGitRep gitrepo.ClusterGitRepo, + manager *managerparam.Manager) Service { + return &service{ + appSvc: applicationSvc, + clusterMgr: manager.ClusterMgr, + trMgr: manager.TemplateReleaseMgr, + tagMgr: manager.TagMgr, + clusterGitRepo: clusterGitRep, + } } func (s service) GetByID(ctx context.Context, id uint) (*ClusterDetail, error) { - cluster, err := s.clusterManager.GetByID(ctx, id) + cluster, err := s.clusterMgr.GetByID(ctx, id) if err != nil { return nil, err } - application, err := s.applicationService.GetByID(ctx, cluster.ApplicationID) + application, err := s.appSvc.GetByID(ctx, cluster.ApplicationID) if err != nil { return nil, err } @@ -52,9 +78,46 @@ func (s service) GetByID(ctx context.Context, id uint) (*ClusterDetail, error) { return clusterDetail, nil } -func NewService(applicationSvc applicationservice.Service, manager *managerparam.Manager) Service { - return &service{ - applicationService: applicationSvc, - clusterManager: manager.ClusterMgr, +func (s service) SyncDBWithGitRepo(ctx context.Context, application *appmodels.Application, + cluster *clustermodels.Cluster) (*clustermodels.Cluster, error) { + templateFromFile, err := s.clusterGitRepo.GetClusterTemplate(ctx, application.Name, cluster.Name) + if err != nil { + return nil, err + } + cluster.Template = templateFromFile.Name + cluster.TemplateRelease = templateFromFile.Release + cluster, err = s.clusterMgr.UpdateByID(ctx, cluster.ID, cluster) + if err != nil { + return nil, err + } + + files, err := s.clusterGitRepo.GetClusterValueFiles(ctx, application.Name, cluster.Name) + if err != nil { + return nil, err + } + + for _, file := range files { + if file.FileName == common.GitopsFileTags { + release, err := s.trMgr.GetByTemplateNameAndRelease(ctx, cluster.Template, cluster.TemplateRelease) + if err != nil { + return nil, err + } + midMap := file.Content[release.ChartName].(map[string]interface{}) + tagsMap := midMap[common.GitopsKeyTags].(map[string]interface{}) + tags := make([]*tmodels.TagBasic, 0, len(tagsMap)) + for k, v := range tagsMap { + value, ok := v.(string) + if !ok { + continue + } + tags = append(tags, &tmodels.TagBasic{ + Key: k, + Value: value, + }) + } + return cluster, s.tagMgr.UpsertByResourceTypeID(ctx, + common.ResourceCluster, cluster.ID, tags) + } } + return cluster, nil } diff --git a/pkg/cluster/service/service_test.go b/pkg/cluster/service/service_test.go index 65c2c9db5..c1d228440 100644 --- a/pkg/cluster/service/service_test.go +++ b/pkg/cluster/service/service_test.go @@ -69,8 +69,8 @@ func TestServiceGetByID(t *testing.T) { t.Run("GetByID", func(t *testing.T) { s := service{ - applicationService: applicationservice.NewService(groupservice.NewService(manager), manager), - clusterManager: manager.ClusterMgr, + appSvc: applicationservice.NewService(groupservice.NewService(manager), manager), + clusterMgr: manager.ClusterMgr, } result, err := s.GetByID(ctx, application.ID) assert.Nil(t, err) diff --git a/pkg/event/manager/manager.go b/pkg/event/manager/manager.go index 0d39e4b53..9758bc1c4 100644 --- a/pkg/event/manager/manager.go +++ b/pkg/event/manager/manager.go @@ -142,6 +142,7 @@ var supportedEvents = map[string]string{ models.MemberDeleted: "Member has been deleted", models.PipelinerunCreated: "New pipelinerun has been created", models.PipelinerunCancelled: "Pipelinerun has been cancelled", + models.PipelinerunExecuted: "Pipelinerun has been executed", } func (m *manager) ListSupportEvents() map[string]string { diff --git a/pkg/event/models/event.go b/pkg/event/models/event.go index d9d0cfdab..c373a829b 100644 --- a/pkg/event/models/event.go +++ b/pkg/event/models/event.go @@ -43,6 +43,7 @@ const ( MemberDeleted string = "members_deleted" PipelinerunCreated string = "pipelineruns_created" PipelinerunCancelled string = "pipelineruns_cancelled" + PipelinerunExecuted string = "pipelineruns_executed" // TODO: add group events ) diff --git a/pkg/pr/dao/pipelinerun.go b/pkg/pr/dao/pipelinerun.go index 5169b02e1..a2149b9a8 100644 --- a/pkg/pr/dao/pipelinerun.go +++ b/pkg/pr/dao/pipelinerun.go @@ -16,6 +16,7 @@ package dao import ( "context" + "time" "gorm.io/gorm" @@ -158,7 +159,20 @@ func (d *pipelinerunDAO) GetLatestSuccessByClusterID(ctx context.Context, cluste } func (d *pipelinerunDAO) UpdateStatusByID(ctx context.Context, pipelinerunID uint, status models.PipelineStatus) error { - return d.UpdateColumns(ctx, pipelinerunID, map[string]interface{}{"status": string(status)}) + switch status { + case models.StatusRunning: + return d.UpdateColumns(ctx, pipelinerunID, map[string]interface{}{ + "started_at": time.Now(), + "status": string(status)}, + ) + case models.StatusOK: + return d.UpdateColumns(ctx, pipelinerunID, map[string]interface{}{ + "finished_at": time.Now(), + "status": string(status)}, + ) + default: + return d.UpdateColumns(ctx, pipelinerunID, map[string]interface{}{"status": string(status)}) + } } func (d *pipelinerunDAO) UpdateCIEventIDByID(ctx context.Context, pipelinerunID uint, ciEventID string) error { diff --git a/pkg/pr/manager/pipelinerun_test.go b/pkg/pr/manager/pipelinerun_test.go index 9addfbbf2..de27e86aa 100644 --- a/pkg/pr/manager/pipelinerun_test.go +++ b/pkg/pr/manager/pipelinerun_test.go @@ -47,7 +47,7 @@ func Test(t *testing.T) { ID: 0, ClusterID: 1, Action: models.ActionBuildDeploy, - Status: "created", + Status: string(models.StatusCreated), Title: "title", Description: "description", GitURL: "", @@ -69,10 +69,10 @@ func Test(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "title", prGet.Title) assert.Equal(t, "1", prGet.ConfigCommit) + assert.Equal(t, "created", prGet.Status) err = mgr.UpdateConfigCommitByID(ctx, prGet.ID, "2") assert.Nil(t, err) - prGet, err = mgr.GetByID(ctx, pr.ID) assert.Nil(t, err) assert.Equal(t, "2", prGet.ConfigCommit) @@ -86,6 +86,22 @@ func Test(t *testing.T) { prGet, err = mgr.GetByID(ctx, pr.ID) assert.Nil(t, err) assert.Equal(t, prGet.Status, string(models.StatusMerged)) + assert.Nil(t, prGet.StartedAt) + assert.Nil(t, prGet.FinishedAt) + + err = mgr.UpdateStatusByID(ctx, pr.ID, models.StatusRunning) + assert.Nil(t, err) + prGet, err = mgr.GetByID(ctx, pr.ID) + assert.Nil(t, err) + assert.Equal(t, prGet.Status, string(models.StatusRunning)) + assert.NotNil(t, prGet.StartedAt) + + err = mgr.UpdateStatusByID(ctx, pr.ID, models.StatusOK) + assert.Nil(t, err) + prGet, err = mgr.GetByID(ctx, pr.ID) + assert.Nil(t, err) + assert.Equal(t, prGet.Status, string(models.StatusOK)) + assert.NotNil(t, prGet.FinishedAt) err = mgr.UpdateResultByID(ctx, pr.ID, &models.Result{ S3Bucket: "bucket", diff --git a/pkg/util/sets/set_test.go b/pkg/util/sets/set_test.go index 8cda851c6..a240bff41 100644 --- a/pkg/util/sets/set_test.go +++ b/pkg/util/sets/set_test.go @@ -19,6 +19,8 @@ package sets import ( "reflect" "testing" + + "github.com/stretchr/testify/assert" ) func TestStringSet(t *testing.T) { @@ -268,3 +270,31 @@ func TestStringIntersection(t *testing.T) { } } } + +func TestStringKeySet(t *testing.T) { + m := map[string]struct{}{ + "1": {}, + "2": {}, + "3": {}, + } + s := StringKeySet(m) + assert.Equal(t, 3, s.Len()) +} + +func TestUnsortedList(t *testing.T) { + s := NewString("a", "b", "c", "d") + list := s.UnsortedList() + assert.Equal(t, 4, len(list)) + t.Logf("UnsortedList: %v", list) +} + +func TestPopAny(t *testing.T) { + s := NewString("a", "b", "c", "d") + list := s.UnsortedList() + assert.Equal(t, 4, len(list)) + for i := 0; i < 4; i++ { + item, ok := s.PopAny() + assert.True(t, ok) + assert.Contains(t, list, item) + } +} diff --git a/pkg/util/wlog/log_test.go b/pkg/util/wlog/log_test.go index 0767c58e8..f06d0d281 100644 --- a/pkg/util/wlog/log_test.go +++ b/pkg/util/wlog/log_test.go @@ -75,3 +75,13 @@ func TestResponse(t *testing.T) { } common.Response(ctx, resp) } + +func TestGetDuration(t *testing.T) { + ctx := log.WithContext(context.Background(), "traceId") + + const op = "app: create application" + l := Start(ctx, op) + log.Info(ctx, "hello world") + t.Logf("duration: %v", l.GetDuration()) + l.StopPrint() +}