123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- 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
- }
|