This commit is contained in:
Adam Majer 2024-08-01 18:10:45 +02:00
parent f48a5b62f5
commit 641885a2d7
2 changed files with 256 additions and 49 deletions

View File

@ -8,6 +8,7 @@ import (
"log"
"net/http"
"net/url"
"slices"
)
type ObsClient struct {
@ -179,7 +180,7 @@ func (c *ObsClient) DeleteProject(project string) error {
}
type BuildStatus struct {
type PackageBuildStatus struct {
Package string `xml:"package,attr"`
Code string `xml:"code,attr"`
Details string `xml:"details"`
@ -190,7 +191,8 @@ type BuildResult struct {
Repository string `xml:"repository,attr"`
Arch string `xml:"arch,attr"`
Code string `xml:"code,attr"`
Status []BuildStatus `xml:"status"`
Dirty bool `xml:"dirty,attr"`
Status []PackageBuildStatus `xml:"status"`
Binaries []BinaryList `xml:"binarylist"`
}
@ -210,6 +212,193 @@ type BuildResultList struct {
Result []BuildResult `xml:"result"`
}
func (r *BuildResultList) GetPackageList() []string {
pkgList := make([]string, 0, 16)
for _, res := range r.Result {
// TODO: enough to iterate over one result set?
for _, status := range res.Status {
if !slices.Contains(pkgList, status.Package) {
pkgList = append(pkgList, status.Package)
}
}
}
return pkgList
}
func (r *BuildResultList) BuildResultSummary() (success, finished bool) {
finished = len(r.Result) > 0 && len(r.Result[0].Status) > 0
success = finished
for _, resultSet := range r.Result {
repoDetail, ok := ObsRepoStatusDetails[resultSet.Code]
if !ok {
panic("Unknown repo result code: " + resultSet.Code)
}
finished = repoDetail.Finished
if !finished || resultSet.Dirty {
return
}
for _, result := range resultSet.Status {
detail, ok := ObsBuildStatusDetails[result.Code]
if !ok {
panic("Unknown result code: " + result.Code)
}
finished = finished && detail.Finished
success = success && detail.Success
if !finished {
return
}
}
}
return
}
var ObsBuildStatusDetails map[string]ObsBuildStatusDetail
var ObsRepoStatusDetails map[string]ObsBuildStatusDetail
type ObsBuildStatusDetail struct {
Code string
Description string
Finished bool
Success bool
}
func init() {
ObsBuildStatusDetails = make(map[string]ObsBuildStatusDetail)
ObsRepoStatusDetails = make(map[string]ObsBuildStatusDetail)
// package status
ObsBuildStatusDetails["succeeded"] = ObsBuildStatusDetail{
Code: "succeeded",
Description: "Package has built successfully and can be used to build further packages.",
Finished: true,
Success: true,
}
ObsBuildStatusDetails["failed"] = ObsBuildStatusDetail{
Code: "failed",
Description: "The package does not build successfully. No packages have been created. Packages that depend on this package will be built using any previously created packages, if they exist.",
Finished: true,
Success: false,
}
ObsBuildStatusDetails["unresolvable"] = ObsBuildStatusDetail{
Code: "unresolvable",
Description: "The build can not begin, because required packages are either missing or not explicitly defined.",
Finished: true,
Success: false,
}
ObsBuildStatusDetails["broken"] = ObsBuildStatusDetail{
Code: "broken",
Description: "The sources either contain no build description (e.g. specfile), automatic source processing failed or a merge conflict does exist.",
Finished: true,
Success: false,
}
ObsBuildStatusDetails["blocked"] = ObsBuildStatusDetail{
Code: "blocked",
Description: "This package waits for other packages to be built. These can be in the same or other projects.",
Finished: false,
}
ObsBuildStatusDetails["scheduled"] = ObsBuildStatusDetail{
Code: "scheduled",
Description: "A package has been marked for building, but the build has not started yet.",
Finished: false,
}
ObsBuildStatusDetails["dispatching"] = ObsBuildStatusDetail{
Code: "dispatching",
Description: "A package is being copied to a build host. This is an intermediate state before building.",
Finished: false,
}
ObsBuildStatusDetails["building"] = ObsBuildStatusDetail{
Code: "building",
Description: "The package is currently being built.",
Finished: false,
}
ObsBuildStatusDetails["signing"] = ObsBuildStatusDetail{
Code: "signing",
Description: "The package has been built successfully and is assigned to get signed.",
Finished: false,
}
ObsBuildStatusDetails["finished"] = ObsBuildStatusDetail{
Code: "finished",
Description: "The package has been built and signed, but has not yet been picked up by the scheduler. This is an intermediate state prior to 'succeeded' or 'failed'.",
Finished: false,
}
ObsBuildStatusDetails["disabled"] = ObsBuildStatusDetail{
Code: "disabled",
Description: "The package has been disabled from building in project or package metadata. Packages that depend on this package will be built using any previously created packages, if they still exist.",
Finished: true,
Success: true,
}
ObsBuildStatusDetails["excluded"] = ObsBuildStatusDetail{
Code: "excluded",
Description: "The package build has been disabled in package build description (for example in the .spec file) or does not provide a matching build description for the target.",
Finished: true,
Success: true,
}
ObsBuildStatusDetails["locked"] = ObsBuildStatusDetail{
Code: "locked",
Description: "The package is frozen",
Finished: true,
Success: true,
}
ObsBuildStatusDetails["unknown"] = ObsBuildStatusDetail{
Code: "unknown",
Description: "The scheduler has not yet evaluated this package. Should be a short intermediate state for new packages.",
Finished: false,
}
// repo status
ObsRepoStatusDetails["published"] = ObsBuildStatusDetail{
Code: "published",
Description: "Repository has been published",
Finished: true,
}
ObsRepoStatusDetails["publishing"] = ObsBuildStatusDetail{
Code: "publishing",
Description: "Repository is being created right now",
Finished: true,
}
ObsRepoStatusDetails["unpublished"] = ObsBuildStatusDetail{
Code: "unpublished",
Description: "Build finished, but repository publishing is disabled",
Finished: true,
}
ObsRepoStatusDetails["building"] = ObsBuildStatusDetail{
Code: "building",
Description: "Build jobs exist for the repository",
Finished: false,
}
ObsRepoStatusDetails["finished"] = ObsBuildStatusDetail{
Code: "finished",
Description: "Build jobs have been processed, new repository is not yet created",
Finished: true,
}
ObsRepoStatusDetails["blocked"] = ObsBuildStatusDetail{
Code: "blocked",
Description: "No build possible at the moment, waiting for jobs in other repositories",
Finished: false,
}
ObsRepoStatusDetails["broken"] = ObsBuildStatusDetail{
Code: "broken",
Description: "The repository setup is broken, build or publish not possible",
Finished: true,
}
ObsRepoStatusDetails["scheduling"] = ObsBuildStatusDetail{
Code: "scheduling",
Description: "The repository state is being calculated right now",
Finished: false,
}
}
func parseBuildResults(data []byte) (*BuildResultList, error) {
result := BuildResultList{}
err := xml.Unmarshal(data, &result)

View File

@ -10,6 +10,7 @@ import (
"path"
"regexp"
"slices"
"sort"
"strconv"
"strings"
"time"
@ -60,35 +61,50 @@ func getObsProjectAssociatedWithPr(baseProject string, pr *models.PullRequest) s
)
}
type BuildStatusSummary string
const BuildUnresolveable = BuildStatusSummary("unresolveable")
const BuildBuilding = BuildStatusSummary("building")
const BuildFailed = BuildStatusSummary("failed")
const BuildSuccess = BuildStatusSummary("success")
/*
'published' => 'Repository has been published',
'publishing' => 'Repository is being created right now',
'unpublished' => 'Build finished, but repository publishing is disabled',
'building' => 'Build jobs exist for the repository',
'finished' => 'Build jobs have been processed, new repository is not yet created',
'blocked' => 'No build possible at the moment, waiting for jobs in other repositories',
'broken' => 'The repository setup is broken, build or publish not possible',
'scheduling' => 'The repository state is being calculated right now'
*/
type BuildStatus struct {
Status BuildStatusSummary
Detail string
}
var buildStatus map[string]BuildStatus
func processBuildStatusUpdate() {
}
func processBuildStatus(project, refProject *common.BuildResultList) BuildStatusSummary {
return BuildBuilding
type BuildStatusSummary int
const (
BuildStatusSummarySuccess = 1
BuildStatusSummaryFailed = 2
BuildStatusSummaryBuilding = 3
BuildStatusSummaryUnknown = 4
)
func processBuildStatus(h *common.RequestHandler, project, refProject *common.BuildResultList) BuildStatusSummary {
targetResults := project.Result
if _, finished := project.BuildResultSummary(); !finished {
return BuildStatusSummaryBuilding
}
if _, finished := refProject.BuildResultSummary(); !finished {
h.LogError("refProject not finished building??")
return BuildStatusSummaryUnknown
}
// the repositories should be setup equally between the projects. We
// need to verify that packages that are building in `refProject` are not
// failing in the `project`
BuildResultSorter := func(a, b common.BuildResult) int {
if c := strings.Compare(a.Project, b.Project); c != 0 {
return c
}
if c := strings.Compare(a.Repository, b.Repository); c != 0 {
return c
}
if c := strings.Compare(a.Arch, b.Arch); c != 0 {
return c
}
panic("Should not happen -- BuiltResultSorter equal repos?")
}
slices.SortFunc(project.Result, BuildResultSorter)
slices.SortFunc(refProject.Result, BuildResultSorter)
return BuildStatusSummaryUnknown
}
func startBuild(h *common.RequestHandler, pr *models.PullRequest, obsClient *common.ObsClient) error {
@ -267,16 +283,19 @@ func processPullNotification(h *common.RequestHandler, thread *models.Notificati
}
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)
/*
refPrj := string(bytes.TrimSpace(h.GitCatFile(dir, pr.Head.Sha, "project.build")))
if len(refPrj) < 1 {
_, err := h.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find reference project")
if err != nil {
h.LogPlainError(err)
return
}
*/
h.LogError("Cannot find reference project for %s PR#%d", pr.Base.Name, pr.Index)
return
}
if h.HasError() {
h.LogPlainError(h.Error)
return
}
@ -287,33 +306,32 @@ func processPullNotification(h *common.RequestHandler, thread *models.Notificati
// recreate missing project
h.LogError("missing OBS project ... recreating '%s': %v", obsProject, err)
startBuild(h, pr, obsClient)
return
}
h.LogError("failed fetching build status for '%s': %v", obsProject, err)
return
}
refProjectResult, err := obsClient.BuildStatus(refProject)
refProjectResult, err := obsClient.BuildStatus(refPrj, prjResult.GetPackageList()...)
if err != nil {
h.LogError("failed fetching ref project status for '%s': %v", refProject, err)
h.LogError("failed fetching ref project status for '%s': %v", refPrj, err)
}
buildStatus := processBuildStatus(prjResult, refProjectResult)
buildStatus := processBuildStatus(h, prjResult, refProjectResult)
switch buildStatus {
case BuildSuccess:
case BuildStatusSummarySuccess:
_, err := h.AddReviewComment(pr, common.ReviewStateApproved, "Build successful")
if err != nil {
h.LogPlainError(err)
}
case BuildFailed:
case BuildStatusSummaryFailed:
_, err := h.AddReviewComment(pr, common.ReviewStateRequestChanges, "Build failed")
if err != nil {
h.LogPlainError(err)
}
case BuildBuilding:
}
// waiting for build results -- nothing to do
// waiting for build results
// project := getObsProjectAssociatedWithPr(obsClient.HomeProject, pr)
case common.ReviewStateApproved:
// done, mark notification as read
h.Log("processing request for success build ...")