go-common/app/tool/saga/service/command/merge.go
2019-04-22 18:49:16 +08:00

530 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package command
import (
"context"
"fmt"
"runtime/debug"
"go-common/app/tool/saga/model"
"go-common/app/tool/saga/service/notification"
"go-common/library/log"
ggitlab "github.com/xanzy/go-gitlab"
)
func (c *Command) runTryMerge(ctx context.Context, event *model.HookComment, repo *model.Repo) (err error) {
var (
ok bool
canMerge bool
projID = int(event.MergeRequest.SourceProjectID)
mrIID = int(event.MergeRequest.IID)
wip = event.MergeRequest.WorkInProgress
noteID int
taskInfo = &model.TaskInfo{
Event: event,
Repo: repo,
}
)
log.Info("runTryMerge start ... MRIID: %d, Repo Config: %+v", mrIID, repo.Config)
if ok, err = c.dao.ExistMRIID(ctx, mrIID); err != nil || ok {
return
}
if noteID, err = c.gitlab.CreateMRNote(projID, mrIID, "<pre>SAGA 开始执行,请大佬稍后......</pre>"); err != nil {
return
}
taskInfo.NoteID = noteID
// 1, check wip
if wip {
c.gitlab.UpdateMRNote(projID, mrIID, noteID, "<pre>警告当前MR处于WIP状态请待开发结束后再merge</pre>")
return
}
// 2, check labels
if ok, err = c.checkLabels(projID, mrIID, noteID, repo); err != nil || !ok {
return
}
// 3, check merge status
if canMerge, err = c.checkMergeStatus(projID, mrIID, noteID); err != nil || !canMerge {
return
}
// 4, check pipeline status
if repo.Config.RelatePipeline {
if repo.Config.DelayMerge {
if ok, _, err = c.checkPipeline(projID, mrIID, noteID, 0, model.QueryProcessing); err != nil || !ok {
return
}
} else {
if ok, _, err = c.checkPipeline(projID, mrIID, noteID, 0, model.QuerySuccess); err != nil || !ok {
return
}
}
}
// 5, check path auth
if ok, err = c.checkAllPathAuth(taskInfo); err != nil || !ok {
return
}
// 6, show current mr queue info
c.showMRQueueInfo(ctx, taskInfo)
if err = c.dao.PushMergeTask(ctx, model.TaskStatusWaiting, taskInfo); err != nil {
return
}
if err = c.dao.AddMRIID(ctx, mrIID, int(repo.Config.LockTimeout)); err != nil {
return
}
log.Info("runTryMerge merge task 已加入 waiting 任务列队中... MRIID: %d", mrIID)
return
}
func (c *Command) execMergeTask(taskInfo *model.TaskInfo) (err error) {
var (
ctx = context.TODO()
projID = int(taskInfo.Event.MergeRequest.SourceProjectID)
mrIID = int(taskInfo.Event.MergeRequest.IID)
sourceBranch = taskInfo.Event.MergeRequest.SourceBranch
pipeline = &ggitlab.Pipeline{}
noteID = taskInfo.NoteID
mergeInfo = &model.MergeInfo{
ProjID: projID,
MRIID: mrIID,
URL: taskInfo.Event.ObjectAttributes.URL,
AuthBranches: taskInfo.Repo.Config.AuthBranches,
SourceBranch: taskInfo.Event.MergeRequest.SourceBranch,
TargetBranch: taskInfo.Event.MergeRequest.TargetBranch,
AuthorID: int(taskInfo.Event.MergeRequest.AuthorID),
UserName: taskInfo.Event.User.UserName,
MinReviewer: taskInfo.Repo.Config.MinReviewer,
LockTimeout: taskInfo.Repo.Config.LockTimeout,
Title: taskInfo.Event.MergeRequest.Title,
Description: taskInfo.Event.MergeRequest.Description,
}
)
mergeInfo.NoteID = noteID
// 从等待任务列队移除
if err = c.dao.DeleteMergeTask(ctx, model.TaskStatusWaiting, taskInfo); err != nil {
return
}
// 加入到正在执行任务列队
if err = c.dao.PushMergeTask(ctx, model.TaskStatusRunning, taskInfo); err != nil {
return
}
if taskInfo.Repo.Config.RelatePipeline {
if taskInfo.Repo.Config.DelayMerge {
if err = c.HookDelayMerge(projID, sourceBranch, mergeInfo); err != nil {
return
}
return
}
if err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, "<pre>SAGA 提示为了保证合进主干后能正常编译正在重跑pipeline等待时间取决于pipeline运行时间请耐心等待</pre>"); err != nil {
return
}
if pipeline, err = c.retryPipeline(taskInfo.Event); err != nil {
return
}
mergeInfo.PipelineID = pipeline.ID
if err = c.dao.SetMergeInfo(ctx, projID, sourceBranch, mergeInfo); err != nil {
return
}
} else {
if err = c.HookMerge(projID, sourceBranch, mergeInfo); err != nil {
return
}
}
return
}
func (c *Command) retryPipeline(event *model.HookComment) (pipeline *ggitlab.Pipeline, err error) {
var (
trigger *ggitlab.PipelineTrigger
triggers []*ggitlab.PipelineTrigger
projID = int(event.MergeRequest.SourceProjectID)
sourceBranch = event.MergeRequest.SourceBranch
)
if triggers, err = c.gitlab.Triggers(projID); err != nil {
return
}
if len(triggers) == 0 {
log.Info("No triggers were found for project %d, try to create it now.", projID)
if trigger, err = c.gitlab.CreateTrigger(projID); err != nil {
return
}
triggers = []*ggitlab.PipelineTrigger{trigger}
}
trigger = triggers[0]
if trigger.Owner == nil || trigger.Owner.ID == 0 {
log.Info("Legacy trigger (without owner), take ownership now.")
if trigger, err = c.gitlab.TakeOwnership(projID, trigger.ID); err != nil {
return
}
}
if pipeline, err = c.gitlab.TriggerPipeline(projID, sourceBranch, trigger.Token); err != nil {
return
}
return
}
// HookPipeline ...
func (c *Command) HookPipeline(projID int, branch string, pipelineID int) (err error) {
var (
ok bool
canMerge bool
mergeInfo *model.MergeInfo
)
defer func() {
if x := recover(); x != nil {
log.Error("HookPipeline: %+v %s", x, debug.Stack())
}
}()
if ok, mergeInfo, err = c.dao.MergeInfo(context.TODO(), projID, branch); err != nil || !ok {
return
}
log.Info("HookPipeline projID: %d, MRIID: %d, branch: %s, pipelineId: %d", projID, mergeInfo.MRIID, branch, mergeInfo.PipelineID)
if pipelineID < mergeInfo.PipelineID {
return
}
defer func() {
if err = c.resetMergeStatus(projID, mergeInfo.MRIID, branch, true); err != nil {
log.Error("resetMergeStatus MRIID: %d, error: %+v", mergeInfo.MRIID, err)
}
}()
// 1, check pipeline id
if ok, _, err = c.checkPipeline(projID, mergeInfo.MRIID, mergeInfo.NoteID, mergeInfo.PipelineID, model.QueryID); err != nil || !ok {
return
}
// 2, check pipeline status
if ok, _, err = c.checkPipeline(projID, mergeInfo.MRIID, mergeInfo.NoteID, 0, model.QuerySuccess); err != nil || !ok {
return
}
// 3, check merge status
if canMerge, err = c.checkMergeStatus(projID, mergeInfo.MRIID, mergeInfo.NoteID); err != nil || !canMerge {
return
}
log.Info("HookPipeline acceptMerge ... MRIID: %d", mergeInfo.MRIID)
if ok, err = c.acceptMerge(mergeInfo); err != nil || !ok {
return
}
return
}
// HookMerge ...
func (c *Command) HookMerge(projID int, branch string, mergeInfo *model.MergeInfo) (err error) {
var (
ok bool
canMerge bool
)
defer func() {
if x := recover(); x != nil {
log.Error("HookMerge: %+v %s", x, debug.Stack())
}
}()
defer func() {
if err = c.resetMergeStatus(projID, mergeInfo.MRIID, branch, true); err != nil {
log.Error("resetMergeStatus MRIID: %d, error: %+v", mergeInfo.MRIID, err)
}
}()
log.Info("HookMerge projID: %d, MRIID: %d, branch: %s", projID, mergeInfo.MRIID, branch)
if canMerge, err = c.checkMergeStatus(projID, mergeInfo.MRIID, mergeInfo.NoteID); err != nil || !canMerge {
return
}
log.Info("HookMerge acceptMerge ... MRIID: %d", mergeInfo.MRIID)
if ok, err = c.acceptMerge(mergeInfo); err != nil || !ok {
return
}
return
}
// HookDelayMerge ...
func (c *Command) HookDelayMerge(projID int, branch string, mergeInfo *model.MergeInfo) (err error) {
var (
ctx = context.TODO()
ok bool
noteID = mergeInfo.NoteID
mrIID = mergeInfo.MRIID
pipelineID int
status string
)
defer func() {
if x := recover(); x != nil {
log.Error("HookDelayMerge: %+v %s", x, debug.Stack())
}
}()
//if ok, pipelineID, err = c.checkPipeline(projID, mrIID, noteID, 0, model.QuerySuccessRmNote); err != nil {
//return
//}
if pipelineID, status, err = c.gitlab.MRPipelineStatus(projID, mrIID); err != nil {
return
}
if status == model.PipelineSuccess || status == model.PipelineSkipped {
ok = true
} else if status != model.PipelineRunning && status != model.PipelinePending {
comment := fmt.Sprintf("<pre>警告pipeline状态异常请确保pipeline状态正常后再执行merge操作</pre>")
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
return
}
log.Info("HookDelayMerge projID: %d, MRIID: %d, branch: %s, pipeline status: %t", projID, mergeInfo.MRIID, branch, ok)
if ok {
if err = c.HookMerge(projID, branch, mergeInfo); err != nil {
return
}
} else {
mergeInfo.PipelineID = pipelineID
if err = c.dao.SetMergeInfo(ctx, projID, branch, mergeInfo); err != nil {
return
}
}
return
}
func (c *Command) acceptMerge(mergeInfo *model.MergeInfo) (ok bool, err error) {
var (
comment string
author string
canMerge bool
state string
authorID = mergeInfo.AuthorID
username = mergeInfo.UserName
projID = mergeInfo.ProjID
mrIID = mergeInfo.MRIID
url = mergeInfo.URL
sourceBranch = mergeInfo.SourceBranch
targetBranch = mergeInfo.TargetBranch
noteID = mergeInfo.NoteID
content = mergeInfo.Title
)
if author, err = c.gitlab.UserName(authorID); err != nil {
return
}
if canMerge, err = c.checkMergeStatus(projID, mrIID, noteID); err != nil {
return
}
if !canMerge {
go notification.WechatAuthor(c.dao, author, url, sourceBranch, targetBranch, comment)
return
}
if len(mergeInfo.Description) > 0 {
content = content + "\n\n" + mergeInfo.Description
}
mergeMSG := fmt.Sprintf("Merge branch [%s] into [%s] by [%s]\n%s", sourceBranch, targetBranch, username, content)
if state, err = c.gitlab.AcceptMR(projID, mrIID, mergeMSG); err != nil || state != model.MRStateMerged {
if err != nil {
comment = fmt.Sprintf("<pre>[%s]尝试合并失败当前状态不允许合并请查看上方merge按钮旁的提示</pre>", username)
} else {
comment = fmt.Sprintf("<pre>[%s]尝试合并失败,请检查当前状态或同步目标分支代码后再试!</pre>", username)
}
go notification.WechatAuthor(c.dao, author, url, sourceBranch, targetBranch, comment)
c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
return
}
ok = true
comment = fmt.Sprintf("<pre>[%s]尝试合并成功!</pre>", username)
go notification.WechatAuthor(c.dao, author, url, sourceBranch, targetBranch, comment)
c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
return
}
func (c *Command) resetMergeStatus(projID int, MRIID int, branch string, taskRunning bool) (err error) {
var (
ctx = context.TODO()
)
log.Info("resetMergeStatus projID: %d, MRIID: %d start", projID, MRIID)
if err = c.dao.UnLock(ctx, fmt.Sprintf(model.SagaRepoLockKey, projID)); err != nil {
log.Error("UnLock error: %+v", err)
}
if err = c.dao.DeleteMergeInfo(ctx, projID, branch); err != nil {
log.Error("DeleteMergeInfo error: %+v", err)
}
if err = c.dao.DeleteMRIID(ctx, MRIID); err != nil {
log.Error("Delete MRIID :%d, error: %+v", MRIID, err)
}
if taskRunning {
if err = c.DeleteRunningTask(projID, MRIID); err != nil {
log.Error("DeleteRunningTask: %+v", err)
}
}
log.Info("resetMergeStatus projID: %d, MRIID: %d end!", projID, MRIID)
return
}
// DeleteRunningTask ...
func (c *Command) DeleteRunningTask(projID int, mrID int) (err error) {
var (
ctx = context.TODO()
taskInfos []*model.TaskInfo
)
if _, taskInfos, err = c.dao.MergeTasks(ctx, model.TaskStatusRunning); err != nil {
return
}
for _, taskInfo := range taskInfos {
pID := int(taskInfo.Event.MergeRequest.SourceProjectID)
mID := int(taskInfo.Event.MergeRequest.IID)
if pID == projID && mID == mrID {
// 从正在运行的任务列队中移除
err = c.dao.DeleteMergeTask(ctx, model.TaskStatusRunning, taskInfo)
return
}
}
return
}
func (c *Command) checkMergeStatus(projID int, mrIID int, noteID int) (canMerge bool, err error) {
var (
wip bool
state string
status string
comment string
)
if wip, state, status, err = c.gitlab.MergeStatus(projID, mrIID); err != nil {
return
}
if wip {
comment = "<pre>SAGA 尝试合并失败当前MR是一项正在进行的工作若已完成请先点击“Resolve WIP status”按钮处理后再+merge</pre>"
} else if state != model.MergeStateOpened {
comment = "<pre>SAGA 尝试合并失败当前MR已经关闭或者已经合并</pre>"
} else if status != model.MergeStatusOk {
comment = "<pre>SAGA 尝试合并失败,请先解决合并冲突!</pre>"
} else {
canMerge = true
}
if len(comment) > 0 {
c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
}
return
}
// checkLabels ios or android need checkout label when release app stage
func (c *Command) checkLabels(projID int, mrIID int, noteID int, repo *model.Repo) (ok bool, err error) {
var (
labels []string
comment = fmt.Sprintf("<pre>警告SAGA 无法执行+merge发版阶段只允许合入指定label的MR</pre>")
)
if len(repo.Config.AllowLabel) <= 0 {
ok = true
return
}
if labels, err = c.gitlab.MergeLabels(projID, mrIID); err != nil {
return
}
log.Info("checkMrLabels MRIID: %d, labels: %+v", mrIID, labels)
for _, label := range labels {
if label == repo.Config.AllowLabel {
ok = true
return
}
}
if err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment); err != nil {
return
}
return
}
func (c *Command) checkPipeline(projID int, mrIID int, noteID int, lastPipelineID int, queryStatus model.QueryStatus) (ok bool, pipelineID int, err error) {
var status string
if pipelineID, status, err = c.gitlab.MRPipelineStatus(projID, mrIID); err != nil {
return
}
log.Info("checkPipeline MRIID: %d, queryStatus: %d, pipeline status: %s", mrIID, queryStatus, status)
// query pipeline id index
if queryStatus == model.QueryID {
if pipelineID > lastPipelineID {
comment := fmt.Sprintf("<pre>警告SAGA 检测到重新提交代码了,+merge中断请重新review代码</pre>")
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
return
}
ok = true
return
}
// query process status
if queryStatus == model.QueryProcessing {
if status == model.PipelineRunning || status == model.PipelinePending {
comment := fmt.Sprintf("<pre>警告pipeline正在运行中暂不能立即merge待pipeline运行通过后会自动执行merge操作</pre>")
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
ok = true
return
} else if status == model.PipelineSuccess || status == model.PipelineSkipped {
ok = true
return
}
comment := fmt.Sprintf("<pre>警告pipeline状态异常请确保pipeline状态正常后再执行merge操作</pre>")
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
return
}
// query success status
if queryStatus == model.QuerySuccess {
if status != model.PipelineSuccess {
comment := fmt.Sprintf("<pre>警告SAGA 无法执行+mergepipeline还未成功请大佬先让pipeline执行通过</pre>")
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
ok = false
return
}
}
ok = true
return
}
// showMRQueueInfo ...
func (c *Command) showMRQueueInfo(ctx context.Context, taskInfo *model.TaskInfo) (err error) {
var (
mrIID = int(taskInfo.Event.MergeRequest.IID)
projID = int(taskInfo.Event.MergeRequest.SourceProjectID)
noteID = taskInfo.NoteID
taskInfos []*model.TaskInfo
comment string
waitNum int
runningNum int
)
if _, taskInfos, err = c.dao.MergeTasks(ctx, model.TaskStatusWaiting); err != nil {
return
}
for _, waitTaskInfo := range taskInfos {
if waitTaskInfo.Event.ProjectID == taskInfo.Event.ProjectID {
waitNum++
}
}
if _, taskInfos, err = c.dao.MergeTasks(ctx, model.TaskStatusRunning); err != nil {
return
}
for _, runningTaskInfo := range taskInfos {
if runningTaskInfo.Event.ProjectID == taskInfo.Event.ProjectID {
runningNum++
}
}
if waitNum > 0 {
comment = fmt.Sprintf("<pre>SAGA 提示:当前还有 [%d] 个 MR 等待合并,请大佬耐心等待!</pre>", waitNum)
c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
} else if runningNum > 0 {
comment = fmt.Sprintf("<pre>SAGA 提示:当前还有 [%d] 个 MR 正在执行,请大佬耐心等待!</pre>", runningNum)
c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
}
return
}