204 lines
5.6 KiB
Go
204 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"log"
|
|
"net/url"
|
|
"regexp"
|
|
"slices"
|
|
"strconv"
|
|
"time"
|
|
|
|
"src.opensuse.org/autogits/common"
|
|
"src.opensuse.org/autogits/common/gitea-generated/models"
|
|
)
|
|
|
|
var configs common.AutogitConfigs
|
|
var reviewRx *regexp.Regexp
|
|
var rejectRx *regexp.Regexp
|
|
var groupName string
|
|
|
|
func InitRegex(groupName string) {
|
|
reviewRx = regexp.MustCompile("^" + groupName + "\\s*:\\s*LGTM")
|
|
rejectRx = regexp.MustCompile("^" + groupName + "\\s*:")
|
|
}
|
|
|
|
func ReviewAccepted(reviewText string) bool {
|
|
return reviewRx.MatchString(reviewText)
|
|
}
|
|
|
|
func ReviewRejected(reviewText string) bool {
|
|
return rejectRx.MatchString(reviewText)
|
|
}
|
|
|
|
func ProcessNotifications(notification *models.NotificationThread, gitea common.Gitea) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
log.Println("--- resovered")
|
|
}
|
|
}()
|
|
|
|
rx := regexp.MustCompile(`^https://src\.(?:open)?suse\.(?:org|de)/api/v\d+/repos/(?<org>[a-zA-Z0-9]+)/(?<project>[_a-zA-Z0-9]+)/issues/(?<num>[0-9]+)$`)
|
|
subject := notification.Subject
|
|
match := rx.FindStringSubmatch(subject.URL)
|
|
if match == nil {
|
|
log.Panicf("** Unexpected format of notification: %s", subject.URL)
|
|
}
|
|
|
|
org := match[1]
|
|
repo := match[2]
|
|
id, _ := strconv.ParseInt(match[3], 10, 64)
|
|
|
|
log.Printf("processing: %s/%s#%d\n", org, repo, id)
|
|
pr, err := gitea.GetPullRequest(org, repo, id)
|
|
if err != nil {
|
|
log.Println(" ** Cannot fetch PR associated with review:", subject.URL, "Error:", err)
|
|
return
|
|
}
|
|
|
|
config := configs.GetPrjGitConfig(org, repo, pr.Base.Name)
|
|
if pr.State == "closed" {
|
|
// dismiss the review
|
|
log.Println(" -- closed request, so nothing to review")
|
|
gitea.SetNotificationRead(notification.ID)
|
|
return
|
|
}
|
|
|
|
reviews, err := gitea.GetPullRequestReviews(org, repo, id)
|
|
if err != nil {
|
|
log.Println(" ** No reviews associated with request:", subject.URL, "Error:", err)
|
|
return
|
|
}
|
|
|
|
requestReviewers, err := config.GetReviewGroupMembers(groupName)
|
|
if err != nil {
|
|
log.Println(err)
|
|
return
|
|
}
|
|
|
|
// submitter cannot be reviewer
|
|
requestReviewers = slices.DeleteFunc(requestReviewers, func(u string) bool { return u == pr.User.UserName })
|
|
|
|
for _, review := range reviews {
|
|
user := review.User.UserName
|
|
if !review.Stale && !review.Dismissed &&
|
|
slices.Contains(requestReviewers, user) {
|
|
|
|
if review.State == common.ReviewStateApproved && ReviewAccepted(review.Body) {
|
|
gitea.AddReviewComment(pr, common.ReviewStateApproved, "Signed off by: "+user)
|
|
if err := gitea.SetNotificationRead(notification.ID); err != nil {
|
|
log.Println(" Cannot set notification as read", err)
|
|
}
|
|
log.Println(" -> approved by", user)
|
|
return
|
|
} else if review.State == common.ReviewStateRequestChanges && ReviewRejected(review.Body) {
|
|
gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Request changes. See review by: "+user)
|
|
if err := gitea.SetNotificationRead(notification.ID); err != nil {
|
|
log.Println(" Cannot set notification as read", err)
|
|
}
|
|
log.Println(" -> declined by", user)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// request group member reviews, if missing
|
|
log.Println(" Requesting reviews for:", requestReviewers)
|
|
if _, err := gitea.RequestReviews(pr, requestReviewers...); err != nil {
|
|
log.Println(" -> err:", err)
|
|
}
|
|
}
|
|
|
|
func PeriodReviewCheck(gitea common.Gitea) {
|
|
notifications, err := gitea.GetPullNotifications(nil)
|
|
if err != nil {
|
|
log.Println(" EEE Error fetching unread notifications: %w", err)
|
|
return
|
|
}
|
|
|
|
for _, notification := range notifications {
|
|
ProcessNotifications(notification, gitea)
|
|
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
giteaUrl := flag.String("gitea-url", "https://src.opensuse.org", "Gitea instance used for reviews")
|
|
rabbitMqHost := flag.String("rabbit-host", "rabbit.opensuse.org", "RabbitMQ instance where Gitea webhook notifications are sent")
|
|
interval := flag.Int64("interval", 5, "Notification polling interval in minutes (min 1 min)")
|
|
configFile := flag.String("config", "", "PrjGit listing config file")
|
|
flag.Parse()
|
|
|
|
args := flag.Args()
|
|
if len(args) != 1 {
|
|
log.Println(" syntax:")
|
|
log.Println(" group-review [OPTIONS] <review-group-name>")
|
|
log.Println()
|
|
flag.Usage()
|
|
return
|
|
}
|
|
groupName = args[0]
|
|
|
|
configData, err := common.ReadConfigFile(*configFile)
|
|
if err != nil {
|
|
log.Println("Failed to read config file", err)
|
|
return
|
|
}
|
|
|
|
if err := common.RequireGiteaSecretToken(); err != nil {
|
|
log.Panicln(err)
|
|
}
|
|
|
|
if err := common.RequireRabbitSecrets(); err != nil {
|
|
log.Panicln(err)
|
|
}
|
|
|
|
gitea := common.AllocateGiteaTransport(*giteaUrl)
|
|
configs, err = common.ResolveWorkflowConfigs(gitea, configData)
|
|
if err != nil {
|
|
log.Panicln(err)
|
|
}
|
|
|
|
reviewer, err := gitea.GetCurrentUser()
|
|
if err != nil {
|
|
log.Panicln("Cannot fetch review user: %w", err)
|
|
}
|
|
|
|
if *interval < 1 {
|
|
*interval = 1
|
|
}
|
|
|
|
InitRegex(groupName)
|
|
|
|
log.Println(" ** processing group reviews for group:", groupName)
|
|
log.Println(" ** username in Gitea:", reviewer.UserName)
|
|
log.Println(" ** polling internval:", *interval, "min")
|
|
log.Println(" ** connecting to RabbitMQ:", *rabbitMqHost)
|
|
|
|
if groupName != reviewer.UserName {
|
|
log.Println(" ***** Reviewer does not match group name. Aborting. *****")
|
|
return
|
|
}
|
|
|
|
_, err = url.Parse("amqps://" + *rabbitMqHost)
|
|
if err != nil {
|
|
log.Panicln("Cannot parse RabbitMQ host:", err)
|
|
}
|
|
/*
|
|
common.ListenDefinitions{
|
|
RabbitURL: u,
|
|
GitAuthor: groupName,
|
|
Orgs: []string{"#"},
|
|
Handlers: map[string]common.RequestProcessor{
|
|
common.RequestType_PRReviewRequest: ReviewRequest,
|
|
common.RequestType_PRReviewRejected: ProcessReview,
|
|
common.RequestType_PRReviewAccepted: ProcessReview,
|
|
},
|
|
}
|
|
*/
|
|
for {
|
|
PeriodReviewCheck(gitea)
|
|
time.Sleep(time.Duration(*interval * int64(time.Minute)))
|
|
}
|
|
}
|