autogits/obs-staging-bot/main.go

328 lines
8.6 KiB
Go
Raw Normal View History

2024-07-07 21:08:41 +02:00
package main
import (
2024-07-26 16:53:09 +02:00
"fmt"
2024-07-18 23:36:41 +02:00
"log"
2024-07-27 20:57:18 +02:00
"net/url"
2024-07-18 17:26:17 +02:00
"os"
2024-07-26 16:53:09 +02:00
"path"
2024-07-18 23:36:41 +02:00
"regexp"
2024-07-27 20:57:18 +02:00
"slices"
2024-07-18 23:36:41 +02:00
"strconv"
2024-07-26 16:53:09 +02:00
"strings"
2024-07-27 20:57:18 +02:00
"time"
2024-07-16 22:05:44 +02:00
2024-07-07 21:08:41 +02:00
"src.opensuse.org/autogits/common"
2024-07-18 23:36:41 +02:00
"src.opensuse.org/autogits/common/gitea-generated/models"
2024-07-07 21:08:41 +02:00
)
const (
2024-07-18 23:36:41 +02:00
GitAuthor = "GiteaBot - Obs Staging"
2024-07-26 16:53:09 +02:00
BotName = "ObsStaging"
2024-07-16 17:05:43 +02:00
ObsBuildBot = "/obsbuild"
2024-07-28 21:25:44 +02:00
Username = "autogits_obs_staging_bot"
2024-07-07 21:08:41 +02:00
)
var GiteaToken string
2024-07-18 23:36:41 +02:00
var runId uint
2024-07-07 21:08:41 +02:00
2024-07-18 23:36:41 +02:00
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", err, msg)
}
}
2024-07-16 22:05:44 +02:00
2024-07-26 16:53:09 +02:00
func fetchPrGit(h *common.RequestHandler, pr *models.PullRequest) error {
// clone PR head and base and return path
2024-07-31 16:52:02 +02:00
if h.HasError() {
return h.Error
}
2024-07-26 16:53:09 +02:00
if _, err := os.Stat(path.Join(h.GitPath, pr.Head.Sha)); os.IsNotExist(err) {
h.GitExec("", "clone", "--depth", "1", pr.Head.Repo.CloneURL, pr.Head.Sha)
h.GitExec(pr.Head.Sha, "fetch", "--depth", "1", "origin", pr.Head.Sha, pr.Base.Sha)
} else if err != nil {
h.Error = err
2024-07-16 22:05:44 +02:00
}
2024-07-26 16:53:09 +02:00
return h.Error
2024-07-16 22:05:44 +02:00
}
2024-07-07 21:08:41 +02:00
2024-07-29 15:28:03 +02:00
func getObsProjectAssociatedWithPr(baseProject string, pr *models.PullRequest) string {
return fmt.Sprintf(
"%s:%s:%s:PR:%d",
baseProject,
common.ObsSafeProjectName(pr.Base.Repo.Owner.UserName),
common.ObsSafeProjectName(pr.Base.Repo.Name),
pr.Index,
)
}
2024-07-31 16:52:02 +02:00
type BuildStatusSummary string
const BuildUnresolveable = BuildStatusSummary("unresolveable")
const BuildBuilding = BuildStatusSummary("building")
const BuildFailed = BuildStatusSummary("failed")
const BuildSuccess = BuildStatusSummary("success")
type BuildStatus struct {
Status BuildStatusSummary
Detail string
}
var buildStatus map[string]BuildStatus
func processBuildStatusUpdate() {
}
func processBuildStatus(project, refProject *common.BuildResultList) BuildStatusSummary {
return BuildBuilding
}
2024-07-18 23:36:41 +02:00
func processPullNotification(h *common.RequestHandler, notification *models.NotificationSubject) {
2024-07-26 16:53:09 +02:00
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]+)$`)
2024-07-18 23:36:41 +02:00
match := rx.FindStringSubmatch(notification.URL)
if match == nil {
log.Panicf("Unexpected format of notification: %s", notification.URL)
}
h.Log("processing")
h.Log("project: %s", match[2])
h.Log("org: %s", match[1])
h.Log("number: %s", match[3])
org := match[1]
repo := match[2]
id, _ := strconv.ParseInt(match[3], 10, 64)
pr, reviews, err := h.GetPullRequestAndReviews(org, repo, id)
if err != nil {
return
}
2024-07-22 17:09:45 +02:00
obsClient, err := common.NewObsClient("api.opensuse.org")
if err != nil {
h.LogPlainError(err)
return
}
2024-07-28 21:25:44 +02:00
reviewRequested := false
for _, reviewer := range pr.RequestedReviewers {
if reviewer.UserName == Username {
reviewRequested = true
break
}
}
if !reviewRequested {
return
}
newReviews := make([]*models.PullReview, 0, len(reviews))
for _, review := range reviews {
if review.User.UserName == Username {
newReviews = append(newReviews, review)
}
}
reviews = newReviews
2024-07-27 20:57:18 +02:00
slices.SortFunc(reviews, func(a, b *models.PullReview) int {
return time.Time(a.Submitted).Compare(time.Time(b.Submitted))
})
2024-07-28 21:25:44 +02:00
for idx := len(reviews) - 1; idx >= 0; idx-- {
2024-07-27 20:57:18 +02:00
review := reviews[idx]
2024-07-18 23:36:41 +02:00
h.Log("state: %s, body: %s, id:%d\n", string(review.State), review.Body, review.ID)
2024-07-26 16:53:09 +02:00
if review.User.UserName != "autogits_obs_staging_bot" {
2024-07-18 23:36:41 +02:00
continue
}
2024-07-26 16:53:09 +02:00
h.Log("processing state...")
2024-07-18 23:36:41 +02:00
switch review.State {
2024-07-22 17:09:45 +02:00
// create build project, if doesn't exist, and add it to pending requests
2024-07-26 16:53:09 +02:00
case common.ReviewStateUnknown, common.ReviewStateRequestReview:
2024-07-29 15:28:03 +02:00
err := fetchPrGit(h, pr)
2024-07-26 16:53:09 +02:00
if err != nil {
2024-07-29 15:28:03 +02:00
h.LogError("Cannot fetch PR git: %s", pr.URL)
return
}
// find modified submodules and new submodules -- build them
dir := pr.Head.Sha
headSubmodules := h.GitSubmoduleList(dir, pr.Head.Sha)
baseSubmodules := h.GitSubmoduleList(dir, pr.Base.Sha)
modifiedOrNew := make([]string, 0, 16)
for pkg, headOid := range headSubmodules {
if baseOid, exists := baseSubmodules[pkg]; !exists || baseOid != headOid {
modifiedOrNew = append(modifiedOrNew, pkg)
}
}
h.Log("repo content fetching ...")
buildPrjBytes := h.GitCatFile(dir, pr.Head.Sha, "project.build")
2024-07-31 16:52:02 +02:00
// buildPrjBytes, err := h.GetPullRequestFileContent(pr, "project.build")
2024-07-29 15:28:03 +02:00
if h.HasError() {
h.LogPlainError(h.Error)
/*
2024-07-31 16:52:02 +02:00
_, err := h.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find reference project")
if err != nil {
h.LogPlainError(err)
}
*/
2024-07-26 16:53:09 +02:00
return
}
buildPrj := strings.TrimSpace(string(buildPrjBytes))
2024-07-27 20:57:18 +02:00
meta, err := obsClient.GetProjectMeta(buildPrj)
2024-07-26 16:53:09 +02:00
if err != nil {
h.Log("error fetching project meta for %s: %v", buildPrj, err)
return
}
// generate new project with paths pointinig back to original repos
// disable publishing
// TODO: escape things here
2024-07-29 15:28:03 +02:00
meta.Name = getObsProjectAssociatedWithPr(obsClient.HomeProject, pr)
2024-07-26 16:53:09 +02:00
meta.Description = fmt.Sprintf(`Pull request build job: %s%s PR#%d`,
"https://src.opensuse.org", pr.Base.Repo.Name, pr.Index)
2024-07-27 20:57:18 +02:00
urlPkg := make([]string, 0, len(modifiedOrNew))
for _, pkg := range modifiedOrNew {
urlPkg = append(urlPkg, "onlybuild="+url.QueryEscape(pkg))
}
meta.ScmSync = pr.Head.Repo.CloneURL + "?" + strings.Join(urlPkg, "&")
2024-07-26 16:53:09 +02:00
meta.Title = fmt.Sprintf("PR#%d to %s", pr.Index, pr.Base.Name)
meta.PublicFlags = common.Flags{Contents: "<disable/>"}
// set paths to parent project
for idx, r := range meta.Repositories {
meta.Repositories[idx].Paths = []common.RepositoryPathMeta{{
Project: buildPrj,
Repository: r.Name,
}}
}
h.Log("%#v", meta)
return
err = obsClient.SetProjectMeta(meta)
if err != nil {
h.LogError("cannot create meta project: %#v", err)
}
2024-07-27 20:57:18 +02:00
// set the review state to pending
2024-07-18 23:36:41 +02:00
case common.ReviewStatePending:
2024-07-31 16:52:02 +02:00
err := fetchPrGit(h, pr)
if err != nil {
h.LogError("Cannot fetch PR git: %s", pr.URL)
return
}
// find modified submodules and new submodules -- build them
dir := pr.Head.Sha
headSubmodules := h.GitSubmoduleList(dir, pr.Head.Sha)
baseSubmodules := h.GitSubmoduleList(dir, pr.Base.Sha)
modifiedOrNew := make([]string, 0, 16)
for pkg, headOid := range headSubmodules {
if baseOid, exists := baseSubmodules[pkg]; !exists || baseOid != headOid {
modifiedOrNew = append(modifiedOrNew, pkg)
}
}
h.Log("repo content fetching ...")
refProject := string(h.GitCatFile(dir, pr.Head.Sha, "project.build"))
// buildPrjBytes, err := h.GetPullRequestFileContent(pr, "project.build")
if h.HasError() {
h.LogPlainError(h.Error)
/*
_, err := h.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find reference project")
if err != nil {
h.LogPlainError(err)
}
*/
return
}
obsProject := getObsProjectAssociatedWithPr(obsClient.HomeProject, pr)
prjResult, err := obsClient.BuildStatus(obsProject)
if err != nil {
h.LogError("failed fetching build status for '%s': %v", obsProject, err)
return
}
refProjectResult, err := obsClient.BuildStatus(refProject)
if err != nil {
h.LogError("failed fetching ref project status for '%s': %v", refProject, err)
}
buildStatus := processBuildStatus(prjResult, refProjectResult)
switch buildStatus {
case BuildSuccess:
_, err := h.AddReviewComment(pr, common.ReviewStateApproved, "Build successful")
if err != nil {
h.LogPlainError(err)
}
case BuildFailed:
_, err := h.AddReviewComment(pr, common.ReviewStateRequestChanges, "Build failed")
if err != nil {
h.LogPlainError(err)
}
case BuildBuilding:
}
2024-07-29 15:28:03 +02:00
// waiting for build results
2024-07-31 16:52:02 +02:00
// project := getObsProjectAssociatedWithPr(obsClient.HomeProject, pr)
2024-07-18 23:36:41 +02:00
case common.ReviewStateApproved:
2024-07-29 15:28:03 +02:00
// done, mark notification as read
h.Log("processing request for success build ...")
2024-07-31 16:52:02 +02:00
2024-07-18 23:36:41 +02:00
case common.ReviewStateRequestChanges:
2024-07-26 16:53:09 +02:00
// build failures, nothing to do here, mark notification as read
2024-07-29 15:28:03 +02:00
h.Log("processing request for failed request changes...")
2024-07-18 23:36:41 +02:00
}
2024-07-27 20:57:18 +02:00
break
2024-07-18 23:36:41 +02:00
}
}
func pollWorkNotifications() {
2024-07-26 16:53:09 +02:00
h := common.CreateRequestHandler(GitAuthor, BotName)
2024-07-18 16:43:27 +02:00
data, err := h.GetNotifications(nil)
if err != nil {
h.LogPlainError(err)
return
}
if data != nil {
for _, notification := range data {
2024-07-18 23:36:41 +02:00
switch notification.Subject.Type {
case "Pull":
processPullNotification(h, notification.Subject)
default:
h.SetNotificationRead(notification.ID)
}
2024-07-18 16:43:27 +02:00
}
}
}
2024-07-16 17:05:43 +02:00
func main() {
2024-07-18 23:36:41 +02:00
failOnError(common.RequireGiteaSecretToken(), "Cannot find GITEA_TOKEN")
failOnError(common.RequireObsSecretToken(), "Cannot find OBS_USER and OBS_PASSWORD")
2024-07-16 22:05:44 +02:00
2024-07-18 23:36:41 +02:00
// go ProcessingObsMessages("rabbit.opensuse.org", "opensuse", "opensuse", "")
2024-07-18 16:43:27 +02:00
2024-07-31 16:52:02 +02:00
// for {
2024-07-18 23:36:41 +02:00
pollWorkNotifications()
2024-07-31 16:52:02 +02:00
// time.Sleep(1000)
// }
2024-07-17 17:20:24 +02:00
2024-07-18 23:36:41 +02:00
stuck := make(chan int)
2024-07-17 17:20:24 +02:00
<-stuck
2024-07-07 21:08:41 +02:00
}