package main import ( "fmt" "log" "net/url" "os" "path" "regexp" "slices" "strconv" "strings" "time" "src.opensuse.org/autogits/common" "src.opensuse.org/autogits/common/gitea-generated/models" ) const ( GitAuthor = "GiteaBot - Obs Staging" BotName = "ObsStaging" ObsBuildBot = "/obsbuild" Username = "autogits_obs_staging_bot" ) var GiteaToken string var runId uint func failOnError(err error, msg string) { if err != nil { log.Panicf("%s: %s", err, msg) } } func fetchPrGit(h *common.RequestHandler, pr *models.PullRequest) error { // clone PR head and base and return path 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 } return h.Error } func processPullNotification(h *common.RequestHandler, notification *models.NotificationSubject) { rx := regexp.MustCompile(`^https://src\.(?:open)?suse\.(?:org|de)/api/v\d+/repos/(?[a-zA-Z0-9]+)/(?[_a-zA-Z0-9]+)/issues/(?[0-9]+)$`) 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 } obsClient, err := common.NewObsClient("api.opensuse.org") if err != nil { h.LogPlainError(err) return } 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 slices.SortFunc(reviews, func(a, b *models.PullReview) int { return time.Time(a.Submitted).Compare(time.Time(b.Submitted)) }) for idx := len(reviews) - 1; idx >= 0; idx-- { review := reviews[idx] h.Log("state: %s, body: %s, id:%d\n", string(review.State), review.Body, review.ID) if review.User.UserName != "autogits_obs_staging_bot" { continue } err := fetchPrGit(h, pr) if err != nil { h.LogError("Cannot fetch PR git: %s", pr.URL) return } 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) } } // find modified submodules and new submodules -- build them h.Log("processing state...") switch review.State { // create build project, if doesn't exist, and add it to pending requests case common.ReviewStateUnknown, common.ReviewStateRequestReview: h.Log("repo content fetching ...") buildPrjBytes, err := h.GetPullRequestFileContent(pr, "project.build") if err != nil { h.LogPlainError(err) _, err := h.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find reference project") if err != nil { h.LogPlainError(err) } return } buildPrj := strings.TrimSpace(string(buildPrjBytes)) meta, err := obsClient.GetProjectMeta(buildPrj) 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 meta.Name = fmt.Sprintf("%s:%s:%s:PR:%d", obsClient.HomeProject, common.ObsSafeProjectName(pr.Base.Repo.Owner.UserName), common.ObsSafeProjectName(pr.Base.Repo.Name), pr.Index, ) meta.Description = fmt.Sprintf(`Pull request build job: %s%s PR#%d`, "https://src.opensuse.org", pr.Base.Repo.Name, pr.Index) 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, "&") meta.Title = fmt.Sprintf("PR#%d to %s", pr.Index, pr.Base.Name) meta.PublicFlags = common.Flags{Contents: ""} // 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) } // set the review state to pending case common.ReviewStatePending: // waiting for build results case common.ReviewStateApproved: // done, mark notification as read case common.ReviewStateRequestChanges: h.Log("processing request for failed request changes...") // build failures, nothing to do here, mark notification as read } break } } func pollWorkNotifications() { h := common.CreateRequestHandler(GitAuthor, BotName) data, err := h.GetNotifications(nil) if err != nil { h.LogPlainError(err) return } if data != nil { for _, notification := range data { switch notification.Subject.Type { case "Pull": processPullNotification(h, notification.Subject) default: h.SetNotificationRead(notification.ID) } } } } func main() { var defs common.ListenDefinitions defs.Url = ObsBuildBot defs.GitAuthor = GitAuthor failOnError(common.RequireGiteaSecretToken(), "Cannot find GITEA_TOKEN") failOnError(common.RequireObsSecretToken(), "Cannot find OBS_USER and OBS_PASSWORD") // go ProcessingObsMessages("rabbit.opensuse.org", "opensuse", "opensuse", "") pollWorkNotifications() /* h := allocateRequestHandler() //" autogits/_ObsPrj/raw/project.build?ref=9680b770855e4fc2e9d9cebbd87e6a0d119693c3e03db187494e3aeff727312f" // https://src.opensuse.org/adamm/autogits/commit/8db4d8c3021fc21baa606b87eaad0e476bb7f624f76cb37b6a1819bdb5f04b43#diff-6e698c75c21cd4458440e8a71408c6301ba3a562 f, err := h.GetRepositoryFileContent( &models.Repository{ Owner: &models.User{ UserName: "adamm", }, Name: "autogits", }, "54e418acaf960c5ed8be7f4a29616ae7b5eb75f797f7ed4d487f79485d056108", "bots-common/obs/client.go") if err != nil { h.LogPlainError(err) } h.Log("len: %d", len(f)) */ stuck := make(chan int) <-stuck }