contributors.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. package command
  2. import (
  3. "context"
  4. "fmt"
  5. "path/filepath"
  6. "strings"
  7. "go-common/app/tool/saga/model"
  8. "go-common/app/tool/saga/service/notification"
  9. "go-common/library/log"
  10. )
  11. type contributor struct {
  12. Owner []string
  13. Author []string
  14. Reviewer []string
  15. }
  16. func readContributor(content []byte) (c *contributor) {
  17. var (
  18. lines []string
  19. lineStr string
  20. curSection string
  21. )
  22. c = &contributor{}
  23. lines = strings.Split(string(content), "\n")
  24. for _, lineStr = range lines {
  25. if lineStr == "" {
  26. continue
  27. }
  28. if strings.Contains(strings.ToLower(lineStr), "owner") {
  29. curSection = "owner"
  30. continue
  31. }
  32. if strings.Contains(strings.ToLower(lineStr), "author") {
  33. curSection = "author"
  34. continue
  35. }
  36. if strings.Contains(strings.ToLower(lineStr), "reviewer") {
  37. curSection = "reviewer"
  38. continue
  39. }
  40. switch curSection {
  41. case "owner":
  42. c.Owner = append(c.Owner, strings.TrimSpace(lineStr))
  43. case "author":
  44. c.Author = append(c.Author, strings.TrimSpace(lineStr))
  45. case "reviewer":
  46. c.Reviewer = append(c.Reviewer, strings.TrimSpace(lineStr))
  47. }
  48. }
  49. return
  50. }
  51. // BuildContributor ...
  52. func (c *Command) BuildContributor(repo *model.RepoInfo) (err error) {
  53. var (
  54. host string
  55. token string
  56. files []string
  57. projID int
  58. branch = repo.Branch
  59. )
  60. if host, token, err = c.gitlab.HostToken(); err != nil {
  61. return
  62. }
  63. if files, err = c.dao.RepoFiles(context.TODO(), host, token, repo); err != nil {
  64. return
  65. }
  66. if projID, err = c.gitlab.ProjectID(fmt.Sprintf("git@%s:%s/%s.git", host, repo.Group, repo.Name)); err != nil {
  67. return
  68. }
  69. if err = c.SaveContributor(projID, branch, files, false); err != nil {
  70. return
  71. }
  72. return
  73. }
  74. func hasbranch(branch string, branchs []string) bool {
  75. for _, b := range branchs {
  76. if strings.EqualFold(b, branch) {
  77. return true
  78. }
  79. }
  80. return false
  81. }
  82. // UpdateContributor ...
  83. func (c *Command) UpdateContributor(projID int, mrIID int, sourceBranch string, targetBranch string, authBranches []string) (err error) {
  84. var changeFiles []string
  85. var deleteFiles []string
  86. if !hasbranch(targetBranch, authBranches) {
  87. log.Info("UpdateContributor not authBranches: %d, %s, %+v", projID, targetBranch, authBranches)
  88. return
  89. }
  90. if changeFiles, deleteFiles, err = c.gitlab.MRChanges(projID, mrIID); err != nil {
  91. return
  92. }
  93. //log.Info("UpdateContributor: projID: %d, changeFiles: %+v, deleteFiles: %+v", projID, changeFiles, deleteFiles)
  94. if err = c.SaveContributor(projID, targetBranch, changeFiles, false); err != nil {
  95. return
  96. }
  97. if err = c.SaveContributor(projID, targetBranch, deleteFiles, true); err != nil {
  98. return
  99. }
  100. return
  101. }
  102. // SaveContributor ...
  103. func (c *Command) SaveContributor(projID int, branch string, files []string, clean bool) (err error) {
  104. var (
  105. ctx = context.TODO()
  106. raw []byte
  107. cb *contributor
  108. )
  109. for _, file := range files {
  110. if strings.EqualFold(filepath.Base(file), model.SagaContributorsName) {
  111. path := filepath.Dir(file)
  112. if clean {
  113. //delete redis key
  114. if err = c.dao.DeletePathAuthR(ctx, projID, branch, path); err != nil {
  115. log.Error("delete Auth Redis key error(%+v) project ID:%d, branch:%s, path:%s", err, projID, branch, path)
  116. err = nil
  117. }
  118. //delete hbase key
  119. if err = c.dao.DeletePathAuthH(ctx, projID, branch, path); err != nil {
  120. log.Error("delete Auth hbase key error(%+v) project ID:%d, branch:%s, path:%s", err, projID, branch, path)
  121. err = nil
  122. }
  123. } else {
  124. if raw, err = c.gitlab.RepoRawFile(projID, branch, file); err != nil {
  125. return
  126. }
  127. cb = readContributor(raw)
  128. log.Info("SaveContributor projectID:%d, branch:%s, path:%s, owner:%+v, reviewer:%+v", projID, branch, path, cb.Owner, cb.Reviewer)
  129. //add to redis
  130. authUser := &model.AuthUsers{
  131. Owners: cb.Owner,
  132. Reviewers: cb.Reviewer,
  133. }
  134. if err = c.dao.SetPathAuthR(ctx, projID, branch, path, authUser); err != nil {
  135. log.Error("Set Auth to Redis error(%+v) project ID:%d, path:%s, owner:%+v, reviewer:%+v", err, projID, path, cb.Owner, cb.Reviewer)
  136. err = nil
  137. }
  138. //add to hbase
  139. if err = c.dao.SetPathAuthH(ctx, projID, branch, path, cb.Owner, cb.Reviewer); err != nil {
  140. 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)
  141. err = nil
  142. }
  143. }
  144. }
  145. }
  146. return
  147. }
  148. // checkSuperAuth ...
  149. func checkSuperAuth(username string, reviewedUsers []string, superUsers []string) (ok bool) {
  150. for _, super := range superUsers {
  151. if strings.EqualFold(super, username) {
  152. return true
  153. }
  154. }
  155. for _, r := range reviewedUsers {
  156. for _, s := range superUsers {
  157. if strings.EqualFold(r, s) {
  158. return true
  159. }
  160. }
  161. }
  162. return false
  163. }
  164. // checkAllPathAuth ...
  165. func (c *Command) checkAllPathAuth(taskInfo *model.TaskInfo) (ok bool, err error) {
  166. var (
  167. comment string
  168. files []string
  169. folders map[string]string
  170. owners []string
  171. reviewers []string
  172. requireOwners []string
  173. requireReviewers []string
  174. reviewedUsers []string
  175. requireReviewFolders []*model.RequireReviewFolder
  176. username = taskInfo.Event.User.UserName
  177. projID = int(taskInfo.Event.MergeRequest.SourceProjectID)
  178. mrIID = int(taskInfo.Event.MergeRequest.IID)
  179. sourceBranch = taskInfo.Event.MergeRequest.SourceBranch
  180. targetBranch = taskInfo.Event.MergeRequest.TargetBranch
  181. authBranch = c.GetAuthBranch(targetBranch, taskInfo.Repo.Config.AuthBranches)
  182. url = taskInfo.Event.ObjectAttributes.URL
  183. minReviewer = taskInfo.Repo.Config.MinReviewer
  184. limitAuth = taskInfo.Repo.Config.LimitAuth
  185. minReviewTip bool
  186. isOwner bool
  187. )
  188. log.Info("checkAllPathAuth start ... MRIID: %d", mrIID)
  189. if reviewedUsers, err = c.reviewedUsers(projID, mrIID); err != nil {
  190. return
  191. }
  192. if len(taskInfo.Repo.Config.SuperAuthUsers) > 0 {
  193. log.Info("checkAllPathAuth MRIID: %d, reviewedUsers: %v, SuperAuthUsers: %v", mrIID, reviewedUsers, taskInfo.Repo.Config.SuperAuthUsers)
  194. ok = checkSuperAuth(username, reviewedUsers, taskInfo.Repo.Config.SuperAuthUsers)
  195. if !ok {
  196. comment, err = c.showRequireSuperAuthComment(taskInfo)
  197. go notification.WechatAuthor(c.dao, username, url, sourceBranch, targetBranch, comment)
  198. }
  199. return
  200. }
  201. if files, err = c.gitlab.CompareDiff(projID, targetBranch, sourceBranch); err != nil {
  202. return
  203. }
  204. log.Info("checkAllPathAuth projID:%d, MRIID:%d, reviewedUsers:%+v, files:%+v, targetBranch:%s, sourceBranch:%s", projID, mrIID, reviewedUsers, files, targetBranch, sourceBranch)
  205. // 去重目录,校验目录权限
  206. folders = make(map[string]string)
  207. for _, file := range files {
  208. folder := filepath.Dir(file)
  209. if _, has := folders[folder]; !has {
  210. folders[folder] = folder
  211. authEnough := false
  212. ownerPathReviewed := false
  213. ownerReviewedStat := false
  214. reviewedCount := 0
  215. requireOwners = []string{}
  216. requireReviewers = []string{}
  217. dir := folder
  218. for {
  219. if owners, reviewers, err = c.GetPathAuth(projID, authBranch, dir); err != nil {
  220. return
  221. }
  222. isOwner, ownerPathReviewed = reviewedOwner(owners, reviewedUsers, username)
  223. num := reviewedNum(reviewers, reviewedUsers)
  224. reviewedCount = reviewedCount + num
  225. if isOwner {
  226. log.Info("checkAllPathAuth user is owner, MRIID: %d, user: %s, owners %v, path: %s", mrIID, username, owners, dir)
  227. authEnough = true
  228. break
  229. }
  230. if ownerPathReviewed {
  231. ownerReviewedStat = true
  232. if reviewedCount >= minReviewer {
  233. 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)
  234. authEnough = true
  235. break
  236. }
  237. }
  238. if (len(requireOwners) == 0) && (len(owners) > 0) {
  239. log.Info("checkAllPathAuth required owners %v, path: %s, MRIID: %d", owners, dir, mrIID)
  240. requireOwners = owners
  241. }
  242. if (len(requireReviewers) == 0) && (len(reviewers) > 0) {
  243. log.Info("checkAllPathAuth required reviewers %v, path: %s, MRIID: %d", reviewers, dir, mrIID)
  244. requireReviewers = reviewers
  245. }
  246. if dir == "." {
  247. break
  248. }
  249. if (len(owners) > 0) && limitAuth {
  250. break
  251. }
  252. dir = filepath.Dir(dir)
  253. }
  254. if !authEnough {
  255. requireAuth := &model.RequireReviewFolder{}
  256. requireAuth.Folder = folder
  257. if !ownerReviewedStat {
  258. requireAuth.Owners = requireOwners
  259. }
  260. if reviewedCount < minReviewer {
  261. requireAuth.Reviewers = requireReviewers
  262. minReviewTip = true
  263. }
  264. requireReviewFolders = append(requireReviewFolders, requireAuth)
  265. }
  266. }
  267. }
  268. if len(requireReviewFolders) > 0 {
  269. comment, err = c.showRequireAuthComment(taskInfo, requireReviewFolders, minReviewTip)
  270. go notification.WechatAuthor(c.dao, username, url, sourceBranch, targetBranch, comment)
  271. return
  272. }
  273. ok = true
  274. return
  275. }
  276. // GetAllPathAuth ...
  277. func (c *Command) GetAllPathAuth(projID int, mrIID int, authBranch string) (pathOwners []model.RequireReviewFolder, err error) {
  278. var (
  279. files []string
  280. deleteFiles []string
  281. folders map[string]string
  282. pathOwner []string
  283. pathReviewer []string
  284. )
  285. if files, deleteFiles, err = c.gitlab.MRChanges(projID, mrIID); err != nil {
  286. return
  287. }
  288. files = append(files, deleteFiles...)
  289. // 去重目录,校验目录权限
  290. folders = make(map[string]string)
  291. for _, file := range files {
  292. folder := filepath.Dir(file)
  293. if _, has := folders[folder]; !has {
  294. folders[folder] = folder
  295. dir := folder
  296. for {
  297. if pathOwner, pathReviewer, err = c.GetPathAuth(projID, authBranch, dir); err != nil {
  298. return
  299. }
  300. if len(pathOwner) > 0 {
  301. exist := false
  302. for _, os := range pathOwners {
  303. if os.Folder == dir {
  304. exist = true
  305. break
  306. }
  307. }
  308. if exist {
  309. break
  310. }
  311. requireOwner := model.RequireReviewFolder{}
  312. requireOwner.Folder = dir
  313. requireOwner.Owners = pathOwner
  314. if len(pathReviewer) > 0 {
  315. requireOwner.Reviewers = pathReviewer
  316. }
  317. pathOwners = append(pathOwners, requireOwner)
  318. break
  319. }
  320. if dir == "." {
  321. break
  322. }
  323. dir = filepath.Dir(dir)
  324. }
  325. }
  326. }
  327. return
  328. }
  329. // GetPathAuth ...
  330. func (c *Command) GetPathAuth(projID int, branch string, path string) (owners []string, reviewers []string, err error) {
  331. var (
  332. ctx = context.TODO()
  333. authUser *model.AuthUsers
  334. )
  335. if authUser, err = c.dao.PathAuthR(ctx, projID, branch, path); err != nil || authUser == nil {
  336. if err != nil {
  337. log.Error("GetPathAuthInfo error project ID:%d, branch:%s, path: %s (err: %+v) ", projID, branch, path, err)
  338. }
  339. if owners, reviewers, err = c.dao.PathAuthH(ctx, projID, branch, path); err != nil {
  340. return
  341. }
  342. if len(owners) <= 0 && len(reviewers) <= 0 {
  343. return
  344. }
  345. if authUser == nil {
  346. authUser = new(model.AuthUsers)
  347. }
  348. authUser.Owners = owners
  349. authUser.Reviewers = reviewers
  350. if err1 := c.dao.SetPathAuthR(ctx, projID, branch, path, authUser); err1 != nil {
  351. log.Error("SetPathAuthR error project ID:%d, branch:%s, path: %s (err1: %+v) ", projID, branch, path, err1)
  352. }
  353. return
  354. }
  355. if authUser != nil {
  356. owners = authUser.Owners
  357. reviewers = authUser.Reviewers
  358. }
  359. return
  360. }
  361. // GetAuthBranch ...
  362. func (c *Command) GetAuthBranch(targetBranch string, authBranches []string) (authBranch string) {
  363. for _, r := range authBranches {
  364. if r == targetBranch {
  365. return r
  366. }
  367. }
  368. return authBranches[0]
  369. }
  370. // showRequireAuthComment ...
  371. func (c *Command) showRequireAuthComment(taskInfo *model.TaskInfo, requireReviewFolders []*model.RequireReviewFolder, minReviewTip bool) (comment string, err error) {
  372. var (
  373. username = taskInfo.Event.User.UserName
  374. mrIID = int(taskInfo.Event.MergeRequest.IID)
  375. projID = int(taskInfo.Event.MergeRequest.SourceProjectID)
  376. noteID = taskInfo.NoteID
  377. minReviewer = taskInfo.Repo.Config.MinReviewer
  378. )
  379. if minReviewTip {
  380. comment = fmt.Sprintf("<pre>[%s]尝试合并失败,无权限的文件目录如下,并且最少需要review人数 [%d] 个,请寻找对应的大佬 +1 后再 +merge:</pre>", username, minReviewer)
  381. } else {
  382. comment = fmt.Sprintf("<pre>[%s]尝试合并失败,无权限的文件目录如下,请寻找对应的大佬 +1 后再 +merge:</pre>", username)
  383. }
  384. comment += "\n"
  385. for _, reviewFolder := range requireReviewFolders {
  386. comment += fmt.Sprintf("+ %s ", reviewFolder.Folder)
  387. if len(reviewFolder.Owners) > 0 {
  388. comment += ";OWNER: "
  389. for _, o := range reviewFolder.Owners {
  390. if o == "all" {
  391. comment += "所有人" + " 或 "
  392. } else {
  393. comment += o + " 或 "
  394. }
  395. }
  396. comment = comment[:len(comment)-len(" 或 ")]
  397. }
  398. if len(reviewFolder.Reviewers) > 0 {
  399. comment += ";REVIEWER: "
  400. for _, o := range reviewFolder.Reviewers {
  401. if o == "all" {
  402. comment += "所有人" + " 与 "
  403. } else {
  404. comment += o + " 与 "
  405. }
  406. }
  407. comment = comment[:len(comment)-len(" 与 ")]
  408. //comment += fmt.Sprintf("<pre>; 已review人数 [%d] 个</pre>", minReviewer)
  409. }
  410. comment += "\n\n"
  411. }
  412. err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
  413. return
  414. }
  415. // showRequireSuperAuthComment ...
  416. func (c *Command) showRequireSuperAuthComment(taskInfo *model.TaskInfo) (comment string, err error) {
  417. var (
  418. username = taskInfo.Event.User.UserName
  419. mrIID = int(taskInfo.Event.MergeRequest.IID)
  420. projID = int(taskInfo.Event.MergeRequest.SourceProjectID)
  421. noteID = taskInfo.NoteID
  422. )
  423. comment = fmt.Sprintf("<pre>[%s]尝试合并失败,已配置超级权限用户,请寻找其中至少一位大佬 +1 后再 +merge:</pre>", username)
  424. comment += "\n"
  425. comment += fmt.Sprintf("+ SUPERMAN: ")
  426. for _, user := range taskInfo.Repo.Config.SuperAuthUsers {
  427. comment += fmt.Sprintf("%s 或 ", user)
  428. }
  429. comment = comment[:len(comment)-len(" 或 ")]
  430. err = c.gitlab.UpdateMRNote(projID, mrIID, noteID, comment)
  431. return
  432. }