brunel/buildqueue/worker.go

295 lines
6.6 KiB
Go
Raw Normal View History

2024-07-29 23:40:26 +02:00
package buildqueue
import (
"brunel/config"
"brunel/domain"
"brunel/packages"
"context"
"fmt"
"log/slog"
"net/http"
2024-07-30 02:24:18 +02:00
"sort"
2024-07-29 23:40:26 +02:00
"time"
"golang.org/x/net/html"
)
func StartPackageQueueWorker(ctx context.Context) {
go func() {
for {
select {
case <-ctx.Done():
return
default:
err := packages.ProcessPackages()
if err != nil {
slog.Error("unable to process packages: " + err.Error())
}
packs := packages.GetPackages()
2024-07-30 02:24:18 +02:00
packs.ForEach(func(k string, v domain.SourcePackage) bool {
2024-07-29 23:40:26 +02:00
needsBuild := false
buildVersion := ""
buildAttempt := 0
errPreviously := false
2024-07-30 02:24:18 +02:00
v.Packages.ForEach(func(k string, v domain.PackageInfo) bool {
2024-07-29 23:40:26 +02:00
if v.Status == domain.Current {
return true
}
2024-07-30 00:49:30 +02:00
version := v.NewVersion
if version == "" {
version = v.Version
}
2024-07-29 23:40:26 +02:00
if v.LastBuildStatus == domain.Error {
errPreviously = true
buildAttempt = 1
}
if v.Status == domain.Missing {
needsBuild = true
2024-07-30 00:49:30 +02:00
buildVersion = version
2024-07-29 23:40:26 +02:00
return false
}
if v.Status == domain.Stale {
needsBuild = true
2024-07-30 00:49:30 +02:00
buildVersion = version
2024-07-29 23:40:26 +02:00
return false
}
return true
})
if needsBuild {
2024-07-30 01:07:21 +02:00
typ := domain.BuildTypeLTO
2024-07-29 23:40:26 +02:00
if errPreviously {
2024-07-30 01:07:21 +02:00
typ = domain.BuildTypeNormal
2024-07-29 23:40:26 +02:00
}
buildItem := domain.BuildQueueItem{
Source: v,
Status: domain.Queued,
Type: typ,
Patch: false,
Rebuild: false,
BuildNumber: buildAttempt,
BuildVersion: buildVersion,
}
err := Add(buildItem)
if err != nil {
slog.Info("unable to add package to queue: " + err.Error())
}
}
return true
})
time.Sleep(1 * time.Hour)
}
}
}()
}
func StartQueueWorker(ctx context.Context) {
go processQueue(ctx)
}
func StartStatusWorker(ctx context.Context) {
go processStatus(ctx)
}
func processStatus(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
2024-07-30 00:49:30 +02:00
q := GetQueue()
2024-07-29 23:40:26 +02:00
itemsToRemove := make([]string, 0)
2024-07-30 00:49:30 +02:00
q.ForEach(func(k string, item domain.BuildQueueItem) bool {
if item.Status != domain.Building {
return true
}
2024-07-30 00:38:23 +02:00
complete, err := CheckIfBuildComplete(ctx, item)
if err != nil && !complete {
slog.Error("unable to check if build is complete: " + err.Error())
}
if complete {
if err != nil {
2024-07-30 02:24:18 +02:00
item.Source.Packages.ForEach(func(k string, v domain.PackageInfo) bool {
2024-07-30 00:38:23 +02:00
v.Status = domain.Error
v.LastBuildStatus = domain.Error
2024-07-29 23:40:26 +02:00
item.Source.Packages.Set(k, v)
return true
})
packages.UpdateSourcePackage(item.Source)
itemsToRemove = append(itemsToRemove, k)
return true
}
2024-07-30 02:24:18 +02:00
item.Source.Packages.ForEach(func(k string, v domain.PackageInfo) bool {
2024-07-30 01:23:23 +02:00
v.Status = domain.Current
2024-07-30 00:38:23 +02:00
v.LastBuildStatus = domain.Built
v.Version = item.BuildVersion
v.NewVersion = ""
item.Source.Packages.Set(k, v)
return true
})
packages.UpdateSourcePackage(item.Source)
itemsToRemove = append(itemsToRemove, k)
return true
2024-07-29 23:40:26 +02:00
}
return true
})
for _, item := range itemsToRemove {
Remove(item)
}
2024-07-30 00:38:23 +02:00
time.Sleep(10 * time.Second)
2024-07-29 23:40:26 +02:00
}
}
}
func processQueue(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
2024-07-30 01:21:06 +02:00
q := GetQueue()
buildingFound := false
q.ForEach(func(k string, item domain.BuildQueueItem) bool {
if item.Status == domain.Building {
buildingFound = true
return false
}
return true
})
if buildingFound {
time.Sleep(30 * time.Second)
continue
}
2024-07-29 23:40:26 +02:00
err := ProcessNext()
if err != nil {
slog.Error("unable to process queue: " + err.Error())
}
}
time.Sleep(30 * time.Second)
}
}
func CheckIfBuildComplete(ctx context.Context, item domain.BuildQueueItem) (bool, error) {
resp, err := http.Get(config.Configs.ActionsUrl)
if err != nil {
return false, err
}
defer resp.Body.Close()
doc, err := html.Parse(resp.Body)
if err != nil {
return false, err
}
buildName := item.Source.Name + "=" + item.BuildVersion
2024-07-30 02:24:18 +02:00
var builds []struct {
isMatch bool
status string
time time.Time
}
var f func(*html.Node) error
f = func(n *html.Node) error {
2024-07-29 23:40:26 +02:00
if n.Type == html.ElementNode && n.Data == "div" {
for _, a := range n.Attr {
2024-07-30 02:24:18 +02:00
if a.Key == "class" && a.Val == "flex-item tw-items-center" {
isMatch, status, buildTime := checkBuildBlock(n, buildName)
2024-07-29 23:40:26 +02:00
if isMatch {
2024-07-30 02:24:18 +02:00
builds = append(builds, struct {
isMatch bool
status string
time time.Time
}{isMatch, status, buildTime})
2024-07-29 23:40:26 +02:00
}
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
2024-07-30 02:24:18 +02:00
if err := f(c); err != nil {
return err
2024-07-29 23:40:26 +02:00
}
}
2024-07-30 02:24:18 +02:00
return nil
}
if err := f(doc); err != nil {
return false, err
}
// Sort builds by time, most recent first
sort.Slice(builds, func(i, j int) bool {
return builds[i].time.After(builds[j].time)
})
if len(builds) == 0 {
slog.Info("No matching builds found", "buildName", buildName)
2024-07-29 23:40:26 +02:00
return false, nil
}
2024-07-30 02:24:18 +02:00
mostRecentBuild := builds[0]
switch mostRecentBuild.status {
case "Success":
return true, nil
case "Failure":
return true, fmt.Errorf("build failed")
case "Running":
return false, nil // Build is still in progress
case "Queued":
return false, nil // Build is still in progress
default:
slog.Warn("Unknown build status", "status", mostRecentBuild.status)
return false, fmt.Errorf("unknown build status: %s", mostRecentBuild.status)
}
2024-07-29 23:40:26 +02:00
}
2024-07-30 02:24:18 +02:00
func checkBuildBlock(n *html.Node, buildName string) (bool, string, time.Time) {
2024-07-29 23:40:26 +02:00
var title string
var status string
2024-07-30 02:24:18 +02:00
var buildTime time.Time
2024-07-29 23:40:26 +02:00
var f func(*html.Node)
f = func(n *html.Node) {
if n.Type == html.ElementNode {
switch n.Data {
case "a":
for _, a := range n.Attr {
if a.Key == "class" && a.Val == "flex-item-title" {
for _, attr := range n.Attr {
if attr.Key == "title" {
title = attr.Val
}
}
}
}
2024-07-30 02:24:18 +02:00
case "div":
2024-07-29 23:40:26 +02:00
for _, a := range n.Attr {
2024-07-30 02:24:18 +02:00
if a.Key == "class" && a.Val == "flex-item-leading" {
for c := n.FirstChild; c != nil; c = c.NextSibling {
if c.Type == html.ElementNode && c.Data == "span" {
for _, attr := range c.Attr {
if attr.Key == "data-tooltip-content" {
status = attr.Val
}
}
}
2024-07-29 23:40:26 +02:00
}
}
}
2024-07-30 02:24:18 +02:00
case "relative-time":
for _, a := range n.Attr {
if a.Key == "datetime" {
buildTime, _ = time.Parse(time.RFC3339, a.Val)
}
}
2024-07-29 23:40:26 +02:00
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c)
}
}
f(n)
2024-07-30 02:24:18 +02:00
return title == buildName, status, buildTime
2024-07-29 23:40:26 +02:00
}