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("
[%s]尝试合并失败,无权限的文件目录如下,并且最少需要review人数 [%d] 个,请寻找对应的大佬 +1 后再 +merge:", username, minReviewer) } else { comment = fmt.Sprintf("
[%s]尝试合并失败,无权限的文件目录如下,请寻找对应的大佬 +1 后再 +merge:", 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("
; 已review人数 [%d] 个", 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("
[%s]尝试合并失败,已配置超级权限用户,请寻找其中至少一位大佬 +1 后再 +merge:", 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 }