Files
autogits/group-review/main.go
2025-04-07 14:24:48 +02:00

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