forked from adamm/autogits
290 lines
8.3 KiB
Go
290 lines
8.3 KiB
Go
package main
|
|
|
|
/*
|
|
* This file is part of Autogits.
|
|
*
|
|
* Copyright © 2024 SUSE LLC
|
|
*
|
|
* Autogits is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
* Foundation, either version 2 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* Autogits is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* Foobar. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
"src.opensuse.org/autogits/common"
|
|
)
|
|
|
|
const (
|
|
AppName = "obs-status-service"
|
|
)
|
|
|
|
var obs *common.ObsClient
|
|
|
|
func ProjectStatusSummarySvg(res []*common.BuildResult) []byte {
|
|
list := common.BuildResultList{
|
|
Result: res,
|
|
}
|
|
pkgs := list.GetPackageList()
|
|
maxLen := 0
|
|
for _, p := range pkgs {
|
|
maxLen = max(maxLen, len(p))
|
|
}
|
|
|
|
width := float32(len(list.Result))*1.5 + float32(maxLen)*0.8
|
|
height := 1.5*float32(maxLen) + 30
|
|
|
|
ret := bytes.Buffer{}
|
|
ret.WriteString(`<svg version="2.0" width="`)
|
|
ret.WriteString(fmt.Sprint(width))
|
|
ret.WriteString(`em" height="`)
|
|
ret.WriteString(fmt.Sprint(height))
|
|
ret.WriteString(`em" xmlns="http://www.w3.org/2000/svg">`)
|
|
ret.WriteString(`<defs>
|
|
<g id="f"> <!-- failed -->
|
|
<rect width="8em" height="1.5em" fill="#800" />
|
|
</g>
|
|
<g id="s"> <!--succeeded-->
|
|
<rect width="8em" height="1.5em" fill="#080" />
|
|
</g>
|
|
<g id="buidling"> <!--building-->
|
|
<rect width="8em" height="1.5em" fill="#880" />
|
|
</g>
|
|
</defs>`)
|
|
|
|
ret.WriteString(`<use href="#f" x="1em" y="2em"/>`)
|
|
ret.WriteString(`</svg>`)
|
|
return ret.Bytes()
|
|
}
|
|
|
|
func LinkToBuildlog(R *common.BuildResult, S *common.PackageBuildStatus) string {
|
|
if R != nil && S != nil {
|
|
switch S.Code {
|
|
case "succeeded", "failed", "building":
|
|
return "/buildlog/" + R.Project + "/" + S.Package + "/" + R.Repository + "/" + R.Arch
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func PackageStatusSummarySvg(pkg string, res []*common.BuildResult) []byte {
|
|
// per repo, per arch status bins
|
|
repo_names := []string{}
|
|
package_names := []string{}
|
|
multibuild_prefix := pkg + ":"
|
|
for _, r := range res {
|
|
if pos, found := slices.BinarySearchFunc(repo_names, r.Repository, strings.Compare); !found {
|
|
repo_names = slices.Insert(repo_names, pos, r.Repository)
|
|
}
|
|
|
|
for _, p := range r.Status {
|
|
if p.Package == pkg || strings.HasPrefix(p.Package, multibuild_prefix) {
|
|
if pos, found := slices.BinarySearchFunc(package_names, p.Package, strings.Compare); !found {
|
|
package_names = slices.Insert(package_names, pos, p.Package)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ret := NewSvg()
|
|
for _, pkg = range package_names {
|
|
// if len(package_names) > 1 {
|
|
ret.WriteTitle(pkg)
|
|
// }
|
|
|
|
for _, name := range repo_names {
|
|
ret.WriteSubtitle(name)
|
|
// print all repo arches here and build results
|
|
for _, r := range res {
|
|
if r.Repository != name {
|
|
continue
|
|
}
|
|
|
|
for _, s := range r.Status {
|
|
if s.Package == pkg {
|
|
link := LinkToBuildlog(r, s)
|
|
ret.WritePackageStatus(link, r.Arch, s.Code, s.Details)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret.GenerateSvg()
|
|
}
|
|
|
|
func BuildStatusSvg(repo *common.BuildResult, status *common.PackageBuildStatus) []byte {
|
|
buildStatus, ok := common.ObsBuildStatusDetails[status.Code]
|
|
if !ok {
|
|
buildStatus = common.ObsBuildStatusDetails["error"]
|
|
}
|
|
fillColor := "#480" // orange
|
|
textColor := "#888"
|
|
if buildStatus.Finished {
|
|
textColor = "#fff"
|
|
|
|
if buildStatus.Success {
|
|
fillColor = "#080"
|
|
} else {
|
|
fillColor = "#800"
|
|
}
|
|
}
|
|
|
|
buildlog := LinkToBuildlog(repo, status)
|
|
startTag := ""
|
|
endTag := ""
|
|
|
|
if len(buildlog) > 0 {
|
|
startTag = "<a href=\"" + buildlog + "\">"
|
|
endTag = "</a>"
|
|
}
|
|
|
|
return []byte(`<svg version="2.0" width="8em" height="1.5em" xmlns="http://www.w3.org/2000/svg">` +
|
|
`<rect width="100%" height="100%" fill="` + fillColor + `"/>` + startTag +
|
|
`<text x="4em" y="1.1em" text-anchor="middle" fill="` + textColor + `">` + buildStatus.Code + `</text>` + endTag + `</svg>`)
|
|
}
|
|
|
|
func main() {
|
|
cert := flag.String("cert-file", "", "TLS certificates file")
|
|
key := flag.String("key-file", "", "Private key for the TLS certificate")
|
|
listen := flag.String("listen", "[::1]:8080", "Listening string")
|
|
disableTls := flag.Bool("no-tls", false, "Disable TLS")
|
|
obsUrl := flag.String("obs-url", "https://api.opensuse.org", "OBS API endpoint for package buildlog information")
|
|
debug := flag.Bool("debug", false, "Enable debug logging")
|
|
// RabbitMQHost := flag.String("rabbit-mq", "amqps://rabbit.opensuse.org", "RabbitMQ message bus server")
|
|
// Topic := flag.String("topic", "opensuse.obs", "RabbitMQ topic prefix")
|
|
flag.Parse()
|
|
|
|
if *debug {
|
|
common.SetLoggingLevel(common.LogLevelDebug)
|
|
}
|
|
|
|
// common.PanicOnError(common.RequireObsSecretToken())
|
|
|
|
var err error
|
|
if obs, err = common.NewObsClient(*obsUrl); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if redisUrl := os.Getenv("REDIS"); len(redisUrl) > 0 {
|
|
RedisConnect(redisUrl)
|
|
} else {
|
|
common.LogError("REDIS needs to contains URL of the OBS Redis instance with login information")
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
for {
|
|
if err := RescanRepositories(); err != nil {
|
|
common.LogError("Failed to rescan repositories.", err)
|
|
}
|
|
time.Sleep(time.Minute * 5)
|
|
}
|
|
}()
|
|
|
|
http.HandleFunc("GET /status/{Project}", func(res http.ResponseWriter, req *http.Request) {
|
|
obsPrj := req.PathValue("Project")
|
|
common.LogInfo(" request: GET /status/" + obsPrj)
|
|
res.WriteHeader(http.StatusBadRequest)
|
|
})
|
|
http.HandleFunc("GET /status/{Project}/{Package}", func(res http.ResponseWriter, req *http.Request) {
|
|
obsPrj := req.PathValue("Project")
|
|
obsPkg := req.PathValue("Package")
|
|
common.LogInfo(" request: GET /status/" + obsPrj + "/" + obsPkg)
|
|
|
|
status := FindAndUpdateProjectResults(obsPrj)
|
|
if len(status) == 0 {
|
|
res.WriteHeader(404)
|
|
return
|
|
}
|
|
svg := PackageStatusSummarySvg(obsPkg, status)
|
|
|
|
res.Header().Add("content-type", "image/svg+xml")
|
|
res.Header().Add("size", fmt.Sprint(len(svg)))
|
|
res.Write(svg)
|
|
})
|
|
http.HandleFunc("GET /status/{Project}/{Package}/{Repository}", func(res http.ResponseWriter, req *http.Request) {
|
|
obsPrj := req.PathValue("Project")
|
|
obsPkg := req.PathValue("Package")
|
|
repo := req.PathValue("Repository")
|
|
common.LogInfo(" request: GET /status/" + obsPrj + "/" + obsPkg)
|
|
|
|
status := FindAndUpdateRepoResults(obsPrj, repo)
|
|
if len(status) == 0 {
|
|
res.WriteHeader(404)
|
|
return
|
|
}
|
|
svg := PackageStatusSummarySvg(obsPkg, status)
|
|
|
|
res.Header().Add("content-type", "image/svg+xml")
|
|
res.Header().Add("size", fmt.Sprint(len(svg)))
|
|
res.Write(svg)
|
|
})
|
|
http.HandleFunc("GET /status/{Project}/{Package}/{Repository}/{Arch}", func(res http.ResponseWriter, req *http.Request) {
|
|
prj := req.PathValue("Project")
|
|
pkg := req.PathValue("Package")
|
|
repo := req.PathValue("Repository")
|
|
arch := req.PathValue("Arch")
|
|
common.LogInfo("GET /status/" + prj + "/" + pkg + "/" + repo + "/" + arch)
|
|
|
|
res.Header().Add("content-type", "image/svg+xml")
|
|
|
|
for _, r := range FindAndUpdateProjectResults(prj) {
|
|
if r.Arch == arch && r.Repository == repo {
|
|
if idx, found := slices.BinarySearchFunc(r.Status, &common.PackageBuildStatus{Package: pkg}, common.PackageBuildStatusComp); found {
|
|
res.Write(BuildStatusSvg(r, r.Status[idx]))
|
|
return
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
res.Write(BuildStatusSvg(nil, &common.PackageBuildStatus{Package: pkg, Code: "unknown"}))
|
|
})
|
|
http.HandleFunc("GET /buildlog/{Project}/{Package}/{Repository}/{Arch}", func(res http.ResponseWriter, req *http.Request) {
|
|
prj := req.PathValue("Project")
|
|
pkg := req.PathValue("Package")
|
|
repo := req.PathValue("Repository")
|
|
arch := req.PathValue("Arch")
|
|
|
|
res.Header().Add("location", "https://build.opensuse.org/package/live_build_log/"+prj+"/"+pkg+"/"+repo+"/"+arch)
|
|
res.WriteHeader(307)
|
|
return
|
|
|
|
// status := GetDetailedBuildStatus(prj, pkg, repo, arch)
|
|
data, err := obs.BuildLog(prj, pkg, repo, arch)
|
|
if err != nil {
|
|
res.WriteHeader(http.StatusInternalServerError)
|
|
common.LogError("Failed to fetch build log for:", prj, pkg, repo, arch, err)
|
|
return
|
|
}
|
|
defer data.Close()
|
|
|
|
io.Copy(res, data)
|
|
})
|
|
|
|
if *disableTls {
|
|
log.Fatal(http.ListenAndServe(*listen, nil))
|
|
} else {
|
|
log.Fatal(http.ListenAndServeTLS(*listen, *cert, *key, nil))
|
|
}
|
|
}
|