You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
470 lines
14 KiB
470 lines
14 KiB
package command |
|
|
|
import ( |
|
"context" |
|
"fmt" |
|
"path/filepath" |
|
"strings" |
|
|
|
"go-common/app/tool/saga/model" |
|
"go-common/app/tool/saga/service/notification" |
|
"go-common/library/log" |
|
) |
|
|
|
type contributor struct { |
|
Owner []string |
|
Author []string |
|
Reviewer []string |
|
} |
|
|
|
func readContributor(content []byte) (c *contributor) { |
|
var ( |
|
lines []string |
|
lineStr string |
|
curSection string |
|
) |
|
c = &contributor{} |
|
lines = strings.Split(string(content), "\n") |
|
for _, lineStr = range lines { |
|
if lineStr == "" { |
|
continue |
|
} |
|
if strings.Contains(strings.ToLower(lineStr), "owner") { |
|
curSection = "owner" |
|
continue |
|
} |
|
if strings.Contains(strings.ToLower(lineStr), "author") { |
|
curSection = "author" |
|
continue |
|
} |
|
if strings.Contains(strings.ToLower(lineStr), "reviewer") { |
|
curSection = "reviewer" |
|
continue |
|
} |
|
switch curSection { |
|
case "owner": |
|
c.Owner = append(c.Owner, strings.TrimSpace(lineStr)) |
|
case "author": |
|
c.Author = append(c.Author, strings.TrimSpace(lineStr)) |
|
case "reviewer": |
|
c.Reviewer = append(c.Reviewer, strings.TrimSpace(lineStr)) |
|
} |
|
} |
|
return |
|
} |
|
|
|
// BuildContributor ... |
|
func (c *Command) BuildContributor(repo *model.RepoInfo) (err error) { |
|
var ( |
|
host string |
|
token string |
|
files []string |
|
projID int |
|
branch = repo.Branch |
|
) |
|
if host, token, err = c.gitlab.HostToken(); err != nil { |
|
return |
|
} |
|
if files, err = c.dao.RepoFiles(context.TODO(), host, token, repo); err != nil { |
|
return |
|
} |
|
if projID, err = c.gitlab.ProjectID(fmt.Sprintf("git@%s:%s/%s.git", host, repo.Group, repo.Name)); err != nil { |
|
return |
|
} |
|
if err = c.SaveContributor(projID, branch, files, false); err != nil { |
|
return |
|
} |
|
return |
|
} |
|
|
|
func hasbranch(branch string, branchs []string) bool { |
|
for _, b := range branchs { |
|
if strings.EqualFold(b, branch) { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
// UpdateContributor ... |
|
func (c *Command) UpdateContributor(projID int, mrIID int, sourceBranch string, targetBranch string, authBranches []string) (err error) { |
|
var changeFiles []string |
|
var deleteFiles []string |
|
if !hasbranch(targetBranch, authBranches) { |
|
log.Info("UpdateContributor not authBranches: %d, %s, %+v", projID, targetBranch, authBranches) |
|
return |
|
} |
|
if changeFiles, deleteFiles, err = c.gitlab.MRChanges(projID, mrIID); err != nil { |
|
return |
|
} |
|
|
|
//log.Info("UpdateContributor: projID: %d, changeFiles: %+v, deleteFiles: %+v", projID, changeFiles, deleteFiles) |
|
if err = c.SaveContributor(projID, targetBranch, changeFiles, false); err != nil { |
|
return |
|
} |
|
if err = c.SaveContributor(projID, targetBranch, deleteFiles, true); err != nil { |
|
return |
|
} |
|
return |
|
} |
|
|
|
// SaveContributor ... |
|
func (c *Command) SaveContributor(projID int, branch string, files []string, clean bool) (err error) { |
|
var ( |
|
ctx = context.TODO() |
|
raw []byte |
|
cb *contributor |
|
) |
|
for _, file := range files { |
|
if strings.EqualFold(filepath.Base(file), model.SagaContributorsName) { |
|
path := filepath.Dir(file) |
|
if clean { |
|
//delete redis key |
|
if err = c.dao.DeletePathAuthR(ctx, projID, branch, path); err != nil { |
|
log.Error("delete Auth Redis key error(%+v) project ID:%d, branch:%s, path:%s", err, projID, branch, path) |
|
err = nil |
|
} |
|
//delete hbase key |
|
if err = c.dao.DeletePathAuthH(ctx, projID, branch, path); err != nil { |
|
log.Error("delete Auth hbase key error(%+v) project ID:%d, branch:%s, path:%s", err, projID, branch, path) |
|
err = nil |
|
} |
|
} else { |
|
if raw, err = c.gitlab.RepoRawFile(projID, branch, file); err != nil { |
|
return |
|
} |
|
cb = readContributor(raw) |
|
log.Info("SaveContributor projectID:%d, branch:%s, path:%s, owner:%+v, reviewer:%+v", projID, branch, path, cb.Owner, cb.Reviewer) |
|
|
|
//add to redis |
|
authUser := &model.AuthUsers{ |
|
Owners: cb.Owner, |
|
Reviewers: cb.Reviewer, |
|
} |
|
if err = c.dao.SetPathAuthR(ctx, projID, branch, path, authUser); err != nil { |
|
log.Error("Set Auth to Redis error(%+v) project ID:%d, path:%s, owner:%+v, reviewer:%+v", err, projID, path, cb.Owner, cb.Reviewer) |
|
err = nil |
|
} |
|
|
|
//add to hbase |
|
if err = c.dao.SetPathAuthH(ctx, projID, branch, path, cb.Owner, cb.Reviewer); err != nil { |
|
log.Error("Set Auth to Hbase error(%+v) projectID:%d, branch:%s, path:%s, owner:%+v, reviewer:%+v", err, projID, branch, path, cb.Owner, cb.Reviewer) |
|
err = nil |
|
} |
|
} |
|
} |
|
} |
|
return |
|
} |
|
|
|
// checkSuperAuth ... |
|
func checkSuperAuth(username string, reviewedUsers []string, superUsers []string) (ok bool) { |
|
for _, super := range superUsers { |
|
if strings.EqualFold(super, username) { |
|
return true |
|
} |
|
} |
|
|
|
for _, r := range reviewedUsers { |
|
for _, s := range superUsers { |
|
if strings.EqualFold(r, s) { |
|
return true |
|
} |
|
} |
|
} |
|
return false |
|
} |
|
|
|
// checkAllPathAuth ... |
|
func (c *Command) checkAllPathAuth(taskInfo *model.TaskInfo) (ok bool, err error) { |
|
var ( |
|
comment string |
|
files []string |
|
folders map[string]string |
|
owners []string |
|
reviewers []string |
|
requireOwners []string |
|
requireReviewers []string |
|
reviewedUsers []string |
|
requireReviewFolders []*model.RequireReviewFolder |
|
username = taskInfo.Event.User.UserName |
|
projID = int(taskInfo.Event.MergeRequest.SourceProjectID) |
|
mrIID = int(taskInfo.Event.MergeRequest.IID) |
|
sourceBranch = taskInfo.Event.MergeRequest.SourceBranch |
|
targetBranch = taskInfo.Event.MergeRequest.TargetBranch |
|
authBranch = c.GetAuthBranch(targetBranch, taskInfo.Repo.Config.AuthBranches) |
|
url = taskInfo.Event.ObjectAttributes.URL |
|
minReviewer = taskInfo.Repo.Config.MinReviewer |
|
limitAuth = taskInfo.Repo.Config.LimitAuth |
|
minReviewTip bool |
|
isOwner bool |
|
) |
|
|
|
log.Info("checkAllPathAuth start ... MRIID: %d", mrIID) |
|
if reviewedUsers, err = c.reviewedUsers(projID, mrIID); err != nil { |
|
return |
|
} |
|
if len(taskInfo.Repo.Config.SuperAuthUsers) > 0 { |
|
log.Info("checkAllPathAuth MRIID: %d, reviewedUsers: %v, SuperAuthUsers: %v", mrIID, reviewedUsers, taskInfo.Repo.Config.SuperAuthUsers) |
|
ok = checkSuperAuth(username, reviewedUsers, taskInfo.Repo.Config.SuperAuthUsers) |
|
if !ok { |
|
comment, err = c.showRequireSuperAuthComment(taskInfo) |
|
go notification.WechatAuthor(c.dao, username, url, sourceBranch, targetBranch, comment) |
|
} |
|
return |
|
} |
|
|
|
if files, err = c.gitlab.CompareDiff(projID, targetBranch, sourceBranch); err != nil { |
|
return |
|
} |
|
|
|
log.Info("checkAllPathAuth projID:%d, MRIID:%d, reviewedUsers:%+v, files:%+v, targetBranch:%s, sourceBranch:%s", projID, mrIID, reviewedUsers, files, targetBranch, sourceBranch) |
|
// 去重目录,校验目录权限 |
|
folders = make(map[string]string) |
|
for _, file := range files { |
|
folder := filepath.Dir(file) |
|
if _, has := folders[folder]; !has { |
|
folders[folder] = folder |
|
authEnough := false |
|
ownerPathReviewed := false |
|
ownerReviewedStat := false |
|
reviewedCount := 0 |
|
requireOwners = []string{} |
|
requireReviewers = []string{} |
|
dir := folder |
|
for { |
|
if owners, reviewers, err = c.GetPathAuth(projID, authBranch, dir); err != nil { |
|
return |
|
} |
|
|
|
isOwner, ownerPathReviewed = reviewedOwner(owners, reviewedUsers, username) |
|
num := reviewedNum(reviewers, reviewedUsers) |
|
reviewedCount = reviewedCount + num |
|
|
|
if isOwner { |
|
log.Info("checkAllPathAuth user is owner, MRIID: %d, user: %s, owners %v, path: %s", mrIID, username, owners, dir) |
|
authEnough = true |
|
break |
|
} |
|
if ownerPathReviewed { |
|
ownerReviewedStat = true |
|
if reviewedCount >= minReviewer { |
|
log.Info("checkAllPathAuth owner reviewed and reach minReviewer, user: %s, owners %+v, path: %s, reviewedUsers: %+v, reviewedCount: %d, minReviewer: %d", username, owners, dir, reviewedUsers, reviewedCount, minReviewer) |
|
authEnough = true |
|
break |
|
} |
|
} |
|
|
|
if (len(requireOwners) == 0) && (len(owners) > 0) { |
|
log.Info("checkAllPathAuth required owners %v, path: %s, MRIID: %d", owners, dir, mrIID) |
|
requireOwners = owners |
|
} |
|
if (len(requireReviewers) == 0) && (len(reviewers) > 0) { |
|
log.Info("checkAllPathAuth required reviewers %v, path: %s, MRIID: %d", reviewers, dir, mrIID) |
|
requireReviewers = reviewers |
|
} |
|
if dir == "." { |
|
break |
|
} |
|
if (len(owners) > 0) && limitAuth { |
|
break |
|
} |
|
dir = filepath.Dir(dir) |
|
} |
|
if !authEnough { |
|
requireAuth := &model.RequireReviewFolder{} |
|
requireAuth.Folder = folder |
|
if !ownerReviewedStat { |
|
requireAuth.Owners = requireOwners |
|
} |
|
if reviewedCount < minReviewer { |
|
requireAuth.Reviewers = requireReviewers |
|
minReviewTip = true |
|
} |
|
requireReviewFolders = append(requireReviewFolders, requireAuth) |
|
} |
|
} |
|
} |
|
if len(requireReviewFolders) > 0 { |
|
comment, err = c.showRequireAuthComment(taskInfo, requireReviewFolders, minReviewTip) |
|
go notification.WechatAuthor(c.dao, username, url, sourceBranch, targetBranch, comment) |
|
return |
|
} |
|
ok = true |
|
return |
|
} |
|
|
|
// GetAllPathAuth ... |
|
func (c *Command) GetAllPathAuth(projID int, mrIID int, authBranch string) (pathOwners []model.RequireReviewFolder, err error) { |
|
var ( |
|
files []string |
|
deleteFiles []string |
|
folders map[string]string |
|
pathOwner []string |
|
pathReviewer []string |
|
) |
|
if files, deleteFiles, err = c.gitlab.MRChanges(projID, mrIID); err != nil { |
|
return |
|
} |
|
files = append(files, deleteFiles...) |
|
// 去重目录,校验目录权限 |
|
folders = make(map[string]string) |
|
for _, file := range files { |
|
folder := filepath.Dir(file) |
|
if _, has := folders[folder]; !has { |
|
folders[folder] = folder |
|
dir := folder |
|
|
|
for { |
|
if pathOwner, pathReviewer, err = c.GetPathAuth(projID, authBranch, dir); err != nil { |
|
return |
|
} |
|
|
|
if len(pathOwner) > 0 { |
|
exist := false |
|
for _, os := range pathOwners { |
|
if os.Folder == dir { |
|
exist = true |
|
break |
|
} |
|
} |
|
if exist { |
|
break |
|
} |
|
|
|
requireOwner := model.RequireReviewFolder{} |
|
requireOwner.Folder = dir |
|
requireOwner.Owners = pathOwner |
|
if len(pathReviewer) > 0 { |
|
requireOwner.Reviewers = pathReviewer |
|
} |
|
|
|
pathOwners = append(pathOwners, requireOwner) |
|
break |
|
} |
|
if dir == "." { |
|
break |
|
} |
|
dir = filepath.Dir(dir) |
|
} |
|
} |
|
} |
|
return |
|
} |
|
|
|
// GetPathAuth ... |
|
func (c *Command) GetPathAuth(projID int, branch string, path string) (owners []string, reviewers []string, err error) { |
|
var ( |
|
ctx = context.TODO() |
|
authUser *model.AuthUsers |
|
) |
|
|
|
if authUser, err = c.dao.PathAuthR(ctx, projID, branch, path); err != nil || authUser == nil { |
|
if err != nil { |
|
log.Error("GetPathAuthInfo error project ID:%d, branch:%s, path: %s (err: %+v) ", projID, branch, path, err) |
|
} |
|
|
|
if owners, reviewers, err = c.dao.PathAuthH(ctx, projID, branch, path); err != nil { |
|
return |
|
} |
|
if len(owners) <= 0 && len(reviewers) <= 0 { |
|
return |
|
} |
|
|
|
if authUser == nil { |
|
authUser = new(model.AuthUsers) |
|
} |
|
authUser.Owners = owners |
|
authUser.Reviewers = reviewers |
|
if err1 := c.dao.SetPathAuthR(ctx, projID, branch, path, authUser); err1 != nil { |
|
log.Error("SetPathAuthR error project ID:%d, branch:%s, path: %s (err1: %+v) ", projID, branch, path, err1) |
|
} |
|
return |
|
} |
|
if authUser != nil { |
|
owners = authUser.Owners |
|
reviewers = authUser.Reviewers |
|
} |
|
return |
|
} |
|
|
|
// GetAuthBranch ... |
|
func (c *Command) GetAuthBranch(targetBranch string, authBranches []string) (authBranch string) { |
|
for _, r := range authBranches { |
|
if r == targetBranch { |
|
return r |
|
} |
|
} |
|
return authBranches[0] |
|
} |
|
|
|
// showRequireAuthComment ... |
|
func (c *Command) showRequireAuthComment(taskInfo *model.TaskInfo, requireReviewFolders []*model.RequireReviewFolder, minReviewTip bool) (comment string, err error) { |
|
var ( |
|
username = taskInfo.Event.User.UserName |
|
mrIID = int(taskInfo.Event.MergeRequest.IID) |
|
projID = int(taskInfo.Event.MergeRequest.SourceProjectID) |
|
noteID = taskInfo.NoteID |
|
minReviewer = taskInfo.Repo.Config.MinReviewer |
|
) |
|
|
|
if minReviewTip { |
|
comment = fmt.Sprintf("<pre>[%s]尝试合并失败,无权限的文件目录如下,并且最少需要review人数 [%d] 个,请寻找对应的大佬 +1 后再 +merge:</pre>", username, minReviewer) |
|
} else { |
|
comment = fmt.Sprintf("<pre>[%s]尝试合并失败,无权限的文件目录如下,请寻找对应的大佬 +1 后再 +merge:</pre>", username) |
|
} |
|
comment += "\n" |
|
|
|
for _, reviewFolder := range requireReviewFolders { |
|
comment += fmt.Sprintf("+ %s ", reviewFolder.Folder) |
|
if len(reviewFolder.Owners) > 0 { |
|
comment += ";OWNER: " |
|
for _, o := range reviewFolder.Owners { |
|
if o == "all" { |
|
comment += "所有人" + " 或 " |
|
} else { |
|
comment += o + " 或 " |
|
} |
|
} |
|
comment = comment[:len(comment)-len(" 或 ")] |
|
} |
|
if len(reviewFolder.Reviewers) > 0 { |
|
comment += ";REVIEWER: " |
|
for _, o := range reviewFolder.Reviewers { |
|
if o == "all" { |
|
comment += "所有人" + " 与 " |
|
} else { |
|
comment += o + " 与 " |
|
} |
|
} |
|
comment = comment[:len(comment)-len(" 与 ")] |
|
//comment += fmt.Sprintf("<pre>; 已review人数 [%d] 个</pre>", minReviewer) |
|
} |
|
comment += "\n\n" |
|
} |
|
|
|
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment) |
|
return |
|
} |
|
|
|
// showRequireSuperAuthComment ... |
|
func (c *Command) showRequireSuperAuthComment(taskInfo *model.TaskInfo) (comment string, err error) { |
|
var ( |
|
username = taskInfo.Event.User.UserName |
|
mrIID = int(taskInfo.Event.MergeRequest.IID) |
|
projID = int(taskInfo.Event.MergeRequest.SourceProjectID) |
|
noteID = taskInfo.NoteID |
|
) |
|
|
|
comment = fmt.Sprintf("<pre>[%s]尝试合并失败,已配置超级权限用户,请寻找其中至少一位大佬 +1 后再 +merge:</pre>", username) |
|
comment += "\n" |
|
|
|
comment += fmt.Sprintf("+ SUPERMAN: ") |
|
for _, user := range taskInfo.Repo.Config.SuperAuthUsers { |
|
comment += fmt.Sprintf("%s 或 ", user) |
|
} |
|
comment = comment[:len(comment)-len(" 或 ")] |
|
|
|
err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment) |
|
return |
|
}
|
|
|