|
Packit Service |
509fd4 |
// Package kojiapi provides a REST API to build and push images to Koji
|
|
Packit Service |
509fd4 |
package kojiapi
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
import (
|
|
Packit Service |
509fd4 |
"crypto/rand"
|
|
Packit Service |
509fd4 |
"encoding/json"
|
|
Packit Service |
509fd4 |
"fmt"
|
|
Packit Service |
509fd4 |
"log"
|
|
Packit Service |
509fd4 |
"math"
|
|
Packit Service |
509fd4 |
"math/big"
|
|
Packit Service |
509fd4 |
"net/http"
|
|
Packit Service |
509fd4 |
"strings"
|
|
Packit Service |
509fd4 |
"time"
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
"github.com/google/uuid"
|
|
Packit Service |
509fd4 |
"github.com/labstack/echo/v4"
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
|
Packit Service |
509fd4 |
"github.com/osbuild/osbuild-composer/internal/distro"
|
|
Packit Service |
509fd4 |
"github.com/osbuild/osbuild-composer/internal/kojiapi/api"
|
|
Packit Service |
509fd4 |
"github.com/osbuild/osbuild-composer/internal/rpmmd"
|
|
Packit Service |
509fd4 |
"github.com/osbuild/osbuild-composer/internal/worker"
|
|
Packit Service |
509fd4 |
)
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// Server represents the state of the koji Server
|
|
Packit Service |
509fd4 |
type Server struct {
|
|
Packit Service |
509fd4 |
logger *log.Logger
|
|
Packit Service |
509fd4 |
workers *worker.Server
|
|
Packit Service |
509fd4 |
rpmMetadata rpmmd.RPMMD
|
|
Packit Service |
509fd4 |
distros *distro.Registry
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// NewServer creates a new koji server
|
|
Packit Service |
509fd4 |
func NewServer(logger *log.Logger, workers *worker.Server, rpmMetadata rpmmd.RPMMD, distros *distro.Registry) *Server {
|
|
Packit Service |
509fd4 |
s := &Server{
|
|
Packit Service |
509fd4 |
logger: logger,
|
|
Packit Service |
509fd4 |
workers: workers,
|
|
Packit Service |
509fd4 |
rpmMetadata: rpmMetadata,
|
|
Packit Service |
509fd4 |
distros: distros,
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return s
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// Create an http.Handler() for this server, that provides the koji API at the
|
|
Packit Service |
509fd4 |
// given path.
|
|
Packit Service |
509fd4 |
func (s *Server) Handler(path string) http.Handler {
|
|
Packit Service |
509fd4 |
e := echo.New()
|
|
Packit Service |
509fd4 |
e.Binder = binder{}
|
|
Packit Service |
509fd4 |
e.StdLogger = s.logger
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// log errors returned from handlers
|
|
Packit Service |
509fd4 |
e.HTTPErrorHandler = func(err error, c echo.Context) {
|
|
Packit Service |
509fd4 |
log.Println(c.Path(), c.QueryParams().Encode(), err.Error())
|
|
Packit Service |
509fd4 |
e.DefaultHTTPErrorHandler(err, c)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
api.RegisterHandlers(e.Group(path), &apiHandlers{s})
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return e
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// apiHandlers implements api.ServerInterface - the http api route handlers
|
|
Packit Service |
509fd4 |
// generated from api/openapi.yml. This is a separate object, because these
|
|
Packit Service |
509fd4 |
// handlers should not be exposed on the `Server` object.
|
|
Packit Service |
509fd4 |
type apiHandlers struct {
|
|
Packit Service |
509fd4 |
server *Server
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// PostCompose handles a new /compose POST request
|
|
Packit Service |
509fd4 |
func (h *apiHandlers) PostCompose(ctx echo.Context) error {
|
|
Packit Service |
509fd4 |
var request api.ComposeRequest
|
|
Packit Service |
509fd4 |
err := ctx.Bind(&request)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return err
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
d := h.server.distros.GetDistro(request.Distribution)
|
|
Packit Service |
509fd4 |
if d == nil {
|
|
Packit Service |
509fd4 |
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported distribution: %s", request.Distribution))
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
type imageRequest struct {
|
|
Packit Service |
509fd4 |
manifest distro.Manifest
|
|
Packit Service |
509fd4 |
arch string
|
|
Packit Service |
509fd4 |
filename string
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
imageRequests := make([]imageRequest, len(request.ImageRequests))
|
|
Packit Service |
509fd4 |
kojiFilenames := make([]string, len(request.ImageRequests))
|
|
Packit Service |
509fd4 |
kojiDirectory := "osbuild-composer-koji-" + uuid.New().String()
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// use the same seed for all images so we get the same IDs
|
|
Packit Service |
509fd4 |
bigSeed, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
panic("cannot generate a manifest seed: " + err.Error())
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
manifestSeed := bigSeed.Int64()
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
for i, ir := range request.ImageRequests {
|
|
Packit Service |
509fd4 |
arch, err := d.GetArch(ir.Architecture)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported architecture '%s' for distribution '%s'", ir.Architecture, request.Distribution))
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
imageType, err := arch.GetImageType(ir.ImageType)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported image type '%s' for %s/%s", ir.ImageType, ir.Architecture, request.Distribution))
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
repositories := make([]rpmmd.RepoConfig, len(ir.Repositories))
|
|
Packit Service |
509fd4 |
for j, repo := range ir.Repositories {
|
|
Packit Service |
509fd4 |
repositories[j].BaseURL = repo.Baseurl
|
|
Packit Service |
509fd4 |
if repo.Gpgkey != nil {
|
|
Packit Service |
509fd4 |
repositories[j].GPGKey = *repo.Gpgkey
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
bp := &blueprint.Blueprint{}
|
|
Packit Service |
509fd4 |
err = bp.Initialize()
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
panic("Could not initialize empty blueprint.")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
15f37d |
|
|
Packit Service |
15f37d |
packageSets := imageType.PackageSets(*bp)
|
|
Packit Service |
15f37d |
packageSpecSets := make(map[string][]rpmmd.PackageSpec)
|
|
Packit Service |
15f37d |
for name, packages := range packageSets {
|
|
Packit Service |
15f37d |
packageSpecs, _, err := h.server.rpmMetadata.Depsolve(packages, repositories, d.ModulePlatformID(), arch.Name())
|
|
Packit Service |
15f37d |
if err != nil {
|
|
Packit Service |
15f37d |
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Failed to depsolve base base packages for %s/%s/%s: %s", ir.ImageType, ir.Architecture, request.Distribution, err))
|
|
Packit Service |
15f37d |
}
|
|
Packit Service |
15f37d |
packageSpecSets[name] = packageSpecs
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
15f37d |
manifest, err := imageType.Manifest(nil, distro.ImageOptions{Size: imageType.Size(0)}, repositories, packageSpecSets, manifestSeed)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("Failed to get manifest for for %s/%s/%s: %s", ir.ImageType, ir.Architecture, request.Distribution, err))
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
imageRequests[i].manifest = manifest
|
|
Packit Service |
509fd4 |
imageRequests[i].arch = arch.Name()
|
|
Packit Service |
509fd4 |
imageRequests[i].filename = imageType.Filename()
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
kojiFilenames[i] = fmt.Sprintf(
|
|
Packit Service |
509fd4 |
"%s-%s-%s.%s%s",
|
|
Packit Service |
509fd4 |
request.Name,
|
|
Packit Service |
509fd4 |
request.Version,
|
|
Packit Service |
509fd4 |
request.Release,
|
|
Packit Service |
509fd4 |
ir.Architecture,
|
|
Packit Service |
509fd4 |
splitExtension(imageType.Filename()),
|
|
Packit Service |
509fd4 |
)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
initID, err := h.server.workers.EnqueueKojiInit(&worker.KojiInitJob{
|
|
Packit Service |
509fd4 |
Server: request.Koji.Server,
|
|
Packit Service |
509fd4 |
Name: request.Name,
|
|
Packit Service |
509fd4 |
Version: request.Version,
|
|
Packit Service |
509fd4 |
Release: request.Release,
|
|
Packit Service |
509fd4 |
})
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
3a6627 |
// This is a programming error.
|
|
Packit Service |
509fd4 |
panic(err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
var buildIDs []uuid.UUID
|
|
Packit Service |
509fd4 |
for i, ir := range imageRequests {
|
|
Packit Service |
509fd4 |
id, err := h.server.workers.EnqueueOSBuildKoji(ir.arch, &worker.OSBuildKojiJob{
|
|
Packit Service |
509fd4 |
Manifest: ir.manifest,
|
|
Packit Service |
509fd4 |
ImageName: ir.filename,
|
|
Packit Service |
509fd4 |
KojiServer: request.Koji.Server,
|
|
Packit Service |
509fd4 |
KojiDirectory: kojiDirectory,
|
|
Packit Service |
509fd4 |
KojiFilename: kojiFilenames[i],
|
|
Packit Service |
509fd4 |
}, initID)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
3a6627 |
// This is a programming error.
|
|
Packit Service |
509fd4 |
panic(err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
buildIDs = append(buildIDs, id)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
id, err := h.server.workers.EnqueueKojiFinalize(&worker.KojiFinalizeJob{
|
|
Packit Service |
509fd4 |
Server: request.Koji.Server,
|
|
Packit Service |
509fd4 |
Name: request.Name,
|
|
Packit Service |
509fd4 |
Version: request.Version,
|
|
Packit Service |
509fd4 |
Release: request.Release,
|
|
Packit Service |
509fd4 |
KojiFilenames: kojiFilenames,
|
|
Packit Service |
509fd4 |
KojiDirectory: kojiDirectory,
|
|
Packit Service |
509fd4 |
TaskID: uint64(request.Koji.TaskId),
|
|
Packit Service |
509fd4 |
StartTime: uint64(time.Now().Unix()),
|
|
Packit Service |
509fd4 |
}, initID, buildIDs)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
3a6627 |
// This is a programming error.
|
|
Packit Service |
509fd4 |
panic(err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// TODO: remove
|
|
Packit Service |
509fd4 |
// For backwards compatibility we must only return once the
|
|
Packit Service |
509fd4 |
// build ID is known. This logic should live in the client,
|
|
Packit Service |
509fd4 |
// and `JobStatus()` should have a way to block until it
|
|
Packit Service |
509fd4 |
// changes.
|
|
Packit Service |
509fd4 |
var initResult worker.KojiInitJobResult
|
|
Packit Service |
509fd4 |
for {
|
|
Packit Service |
509fd4 |
status, _, err := h.server.workers.JobStatus(initID, &initResult)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
panic(err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
if !status.Finished.IsZero() || status.Canceled {
|
|
Packit Service |
509fd4 |
break
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
time.Sleep(500 * time.Millisecond)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
if initResult.KojiError != "" {
|
|
Packit Service |
509fd4 |
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Could not initialize build with koji: %v", initResult.KojiError))
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return ctx.JSON(http.StatusCreated, &api.ComposeResponse{
|
|
Packit Service |
509fd4 |
Id: id.String(),
|
|
Packit Service |
509fd4 |
KojiBuildId: int(initResult.BuildID),
|
|
Packit Service |
509fd4 |
})
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// splitExtension returns the extension of the given file. If there's
|
|
Packit Service |
509fd4 |
// a multipart extension (e.g. file.tar.gz), it returns all parts (e.g.
|
|
Packit Service |
509fd4 |
// .tar.gz). If there's no extension in the input, it returns an empty
|
|
Packit Service |
509fd4 |
// string. If the filename starts with dot, the part before the second dot
|
|
Packit Service |
509fd4 |
// is not considered as an extension.
|
|
Packit Service |
509fd4 |
func splitExtension(filename string) string {
|
|
Packit Service |
509fd4 |
filenameParts := strings.Split(filename, ".")
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
if len(filenameParts) > 0 && filenameParts[0] == "" {
|
|
Packit Service |
509fd4 |
filenameParts = filenameParts[1:]
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
if len(filenameParts) <= 1 {
|
|
Packit Service |
509fd4 |
return ""
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return "." + strings.Join(filenameParts[1:], ".")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
func composeStatusFromJobStatus(js *worker.JobStatus, initResult *worker.KojiInitJobResult, buildResults []worker.OSBuildKojiJobResult, result *worker.KojiFinalizeJobResult) string {
|
|
Packit Service |
509fd4 |
if js.Canceled {
|
|
Packit Service |
509fd4 |
return "failure"
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
if js.Finished.IsZero() {
|
|
Packit Service |
509fd4 |
return "pending"
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
if initResult.KojiError != "" {
|
|
Packit Service |
509fd4 |
return "failure"
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
for _, buildResult := range buildResults {
|
|
Packit Service |
509fd4 |
if buildResult.OSBuildOutput != nil && !buildResult.OSBuildOutput.Success {
|
|
Packit Service |
509fd4 |
return "failure"
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
if buildResult.KojiError != "" {
|
|
Packit Service |
509fd4 |
return "failure"
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
if result.KojiError != "" {
|
|
Packit Service |
509fd4 |
return "failure"
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return "success"
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
func imageStatusFromJobStatus(js *worker.JobStatus, initResult *worker.KojiInitJobResult, buildResult *worker.OSBuildKojiJobResult) string {
|
|
Packit Service |
509fd4 |
if js.Canceled {
|
|
Packit Service |
509fd4 |
return "failure"
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
if initResult.KojiError != "" {
|
|
Packit Service |
509fd4 |
return "failure"
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
if js.Started.IsZero() {
|
|
Packit Service |
509fd4 |
return "pending"
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
if js.Finished.IsZero() {
|
|
Packit Service |
509fd4 |
return "building"
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
if buildResult.OSBuildOutput != nil && buildResult.OSBuildOutput.Success && buildResult.KojiError == "" {
|
|
Packit Service |
509fd4 |
return "success"
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return "failure"
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// GetComposeId handles a /compose/{id} GET request
|
|
Packit Service |
509fd4 |
func (h *apiHandlers) GetComposeId(ctx echo.Context, idstr string) error {
|
|
Packit Service |
509fd4 |
id, err := uuid.Parse(idstr)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
3a6627 |
// Make sure id exists and matches a FinalizeJob
|
|
Packit Service |
3a6627 |
if _, _, err := h.getFinalizeJob(id); err != nil {
|
|
Packit Service |
3a6627 |
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Job %s not found: %s", idstr, err))
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
|
|
Packit Service |
509fd4 |
var finalizeResult worker.KojiFinalizeJobResult
|
|
Packit Service |
509fd4 |
finalizeStatus, deps, err := h.server.workers.JobStatus(id, &finalizeResult)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
3a6627 |
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Job %s not found: %s", idstr, err))
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
3a6627 |
// Make sure deps[0] matches a KojiInitJob
|
|
Packit Service |
3a6627 |
if _, err := h.getInitJob(deps[0]); err != nil {
|
|
Packit Service |
3a6627 |
panic(err)
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
509fd4 |
var initResult worker.KojiInitJobResult
|
|
Packit Service |
509fd4 |
_, _, err = h.server.workers.JobStatus(deps[0], &initResult)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
// this is a programming error
|
|
Packit Service |
509fd4 |
panic(err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
var buildResults []worker.OSBuildKojiJobResult
|
|
Packit Service |
509fd4 |
var imageStatuses []api.ImageStatus
|
|
Packit Service |
509fd4 |
for i := 1; i < len(deps); i++ {
|
|
Packit Service |
3a6627 |
// Make sure deps[i] matches an OSBuildKojiJob
|
|
Packit Service |
3a6627 |
if _, _, err := h.getBuildJob(deps[i]); err != nil {
|
|
Packit Service |
3a6627 |
panic(err)
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
509fd4 |
var buildResult worker.OSBuildKojiJobResult
|
|
Packit Service |
509fd4 |
jobStatus, _, err := h.server.workers.JobStatus(deps[i], &buildResult)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
// this is a programming error
|
|
Packit Service |
509fd4 |
panic(err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
buildResults = append(buildResults, buildResult)
|
|
Packit Service |
509fd4 |
imageStatuses = append(imageStatuses, api.ImageStatus{
|
|
Packit Service |
509fd4 |
Status: imageStatusFromJobStatus(jobStatus, &initResult, &buildResult),
|
|
Packit Service |
509fd4 |
})
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
response := api.ComposeStatus{
|
|
Packit Service |
509fd4 |
Status: composeStatusFromJobStatus(finalizeStatus, &initResult, buildResults, &finalizeResult),
|
|
Packit Service |
509fd4 |
ImageStatuses: imageStatuses,
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
buildID := int(initResult.BuildID)
|
|
Packit Service |
509fd4 |
if buildID != 0 {
|
|
Packit Service |
509fd4 |
response.KojiBuildId = &buildID
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
return ctx.JSON(http.StatusOK, response)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// GetStatus handles a /status GET request
|
|
Packit Service |
509fd4 |
func (h *apiHandlers) GetStatus(ctx echo.Context) error {
|
|
Packit Service |
509fd4 |
return ctx.JSON(http.StatusOK, &api.Status{
|
|
Packit Service |
509fd4 |
Status: "OK",
|
|
Packit Service |
509fd4 |
})
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// Get logs for a compose
|
|
Packit Service |
509fd4 |
func (h *apiHandlers) GetComposeIdLogs(ctx echo.Context, idstr string) error {
|
|
Packit Service |
509fd4 |
id, err := uuid.Parse(idstr)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
3a6627 |
// Make sure id exists and matches a FinalizeJob
|
|
Packit Service |
3a6627 |
if _, _, err := h.getFinalizeJob(id); err != nil {
|
|
Packit Service |
3a6627 |
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Job %s not found: %s", idstr, err))
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
|
|
Packit Service |
509fd4 |
var finalizeResult worker.KojiFinalizeJobResult
|
|
Packit Service |
509fd4 |
_, deps, err := h.server.workers.JobStatus(id, &finalizeResult)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Job %s not found: %s", idstr, err))
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
3a6627 |
// Make sure deps[0] matches a KojiInitJob
|
|
Packit Service |
3a6627 |
if _, err := h.getInitJob(deps[0]); err != nil {
|
|
Packit Service |
3a6627 |
panic(err)
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
|
|
Packit Service |
509fd4 |
var initResult worker.KojiInitJobResult
|
|
Packit Service |
509fd4 |
_, _, err = h.server.workers.JobStatus(deps[0], &initResult)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
3a6627 |
// This is a programming error.
|
|
Packit Service |
509fd4 |
panic(err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
var buildResults []interface{}
|
|
Packit Service |
509fd4 |
for i := 1; i < len(deps); i++ {
|
|
Packit Service |
3a6627 |
// Make sure deps[i] matches an OSBuildKojiJob
|
|
Packit Service |
3a6627 |
if _, _, err := h.getBuildJob(deps[i]); err != nil {
|
|
Packit Service |
3a6627 |
panic(err)
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
509fd4 |
var buildResult worker.OSBuildJobResult
|
|
Packit Service |
509fd4 |
_, _, err = h.server.workers.JobStatus(deps[i], &buildResult)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
// This is a programming error.
|
|
Packit Service |
509fd4 |
panic(err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
buildResults = append(buildResults, buildResult)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// Return the OSBuildJobResults as-is for now. The contents of ImageLogs
|
|
Packit Service |
509fd4 |
// is not part of the API. It's meant for a human to be able to access
|
|
Packit Service |
509fd4 |
// the logs, which just happen to be in JSON.
|
|
Packit Service |
509fd4 |
response := api.ComposeLogs{
|
|
Packit Service |
509fd4 |
KojiInitLogs: initResult,
|
|
Packit Service |
509fd4 |
KojiImportLogs: finalizeResult,
|
|
Packit Service |
509fd4 |
ImageLogs: buildResults,
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return ctx.JSON(http.StatusOK, response)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
3a6627 |
// getFinalizeJob retrieves a KojiFinalizeJob and the IDs of its dependencies
|
|
Packit Service |
3a6627 |
// from the job queue given its ID. It returns an error if the ID matches a
|
|
Packit Service |
3a6627 |
// job of a different type.
|
|
Packit Service |
3a6627 |
func (h *apiHandlers) getFinalizeJob(id uuid.UUID) (*worker.KojiFinalizeJob, []uuid.UUID, error) {
|
|
Packit Service |
3a6627 |
job := new(worker.KojiFinalizeJob)
|
|
Packit Service |
3a6627 |
jobType, _, deps, err := h.server.workers.Job(id, job)
|
|
Packit Service |
3a6627 |
if err != nil {
|
|
Packit Service |
3a6627 |
return nil, nil, err
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
expType := "koji-finalize"
|
|
Packit Service |
3a6627 |
if jobType != expType {
|
|
Packit Service |
3a6627 |
return nil, nil, fmt.Errorf("expected %q, found %q job instead", expType, jobType)
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
return job, deps, err
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
|
|
Packit Service |
3a6627 |
// getInitJob retrieves a KojiInitJob from the job queue given its ID.
|
|
Packit Service |
3a6627 |
// It returns an error if the ID matches a job of a different type.
|
|
Packit Service |
3a6627 |
func (h *apiHandlers) getInitJob(id uuid.UUID) (*worker.KojiInitJob, error) {
|
|
Packit Service |
3a6627 |
job := new(worker.KojiInitJob)
|
|
Packit Service |
3a6627 |
jobType, _, _, err := h.server.workers.Job(id, job)
|
|
Packit Service |
3a6627 |
if err != nil {
|
|
Packit Service |
3a6627 |
return nil, err
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
expType := "koji-init"
|
|
Packit Service |
3a6627 |
if jobType != expType {
|
|
Packit Service |
3a6627 |
return nil, fmt.Errorf("expected %q, found %q job instead", expType, jobType)
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
return job, err
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
|
|
Packit Service |
3a6627 |
// getBuildJob retrieves a OSBuildKojiJob and the IDs of its dependencies from
|
|
Packit Service |
3a6627 |
// the job queue given its ID. It returns an error if the ID matches a job of
|
|
Packit Service |
3a6627 |
// a different type.
|
|
Packit Service |
3a6627 |
func (h *apiHandlers) getBuildJob(id uuid.UUID) (*worker.OSBuildKojiJob, []uuid.UUID, error) {
|
|
Packit Service |
3a6627 |
job := new(worker.OSBuildKojiJob)
|
|
Packit Service |
3a6627 |
jobType, _, deps, err := h.server.workers.Job(id, job)
|
|
Packit Service |
3a6627 |
if err != nil {
|
|
Packit Service |
3a6627 |
return nil, nil, err
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
expType := "osbuild-koji"
|
|
Packit Service |
3a6627 |
if !strings.HasPrefix(jobType, expType) { // Build jobs get automatic arch suffix: Check prefix
|
|
Packit Service |
3a6627 |
return nil, nil, fmt.Errorf("expected %q, found %q job instead", expType, jobType)
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
return job, deps, nil
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
|
|
Packit Service |
3a6627 |
// GetComposeIdManifests returns the Manifests for a given Compose (one for each image).
|
|
Packit Service |
3a6627 |
func (h *apiHandlers) GetComposeIdManifests(ctx echo.Context, idstr string) error {
|
|
Packit Service |
3a6627 |
id, err := uuid.Parse(idstr)
|
|
Packit Service |
3a6627 |
if err != nil {
|
|
Packit Service |
3a6627 |
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
|
|
Packit Service |
3a6627 |
_, deps, err := h.getFinalizeJob(id)
|
|
Packit Service |
3a6627 |
if err != nil {
|
|
Packit Service |
3a6627 |
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Job %s not found: %s", idstr, err))
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
|
|
Packit Service |
3a6627 |
manifests := make([]distro.Manifest, len(deps)-1)
|
|
Packit Service |
3a6627 |
for i, id := range deps[1:] {
|
|
Packit Service |
3a6627 |
buildJob, _, err := h.getBuildJob(id)
|
|
Packit Service |
3a6627 |
if err != nil {
|
|
Packit Service |
3a6627 |
// This is a programming error.
|
|
Packit Service |
3a6627 |
panic(err)
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
manifests[i] = buildJob.Manifest
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
|
|
Packit Service |
3a6627 |
return ctx.JSON(http.StatusOK, manifests)
|
|
Packit Service |
3a6627 |
}
|
|
Packit Service |
3a6627 |
|
|
Packit Service |
509fd4 |
// A simple echo.Binder(), which only accepts application/json, but is more
|
|
Packit Service |
509fd4 |
// strict than echo's DefaultBinder. It does not handle binding query
|
|
Packit Service |
509fd4 |
// parameters either.
|
|
Packit Service |
509fd4 |
type binder struct{}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
func (b binder) Bind(i interface{}, ctx echo.Context) error {
|
|
Packit Service |
509fd4 |
request := ctx.Request()
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
contentType := request.Header["Content-Type"]
|
|
Packit Service |
509fd4 |
if len(contentType) != 1 || contentType[0] != "application/json" {
|
|
Packit Service |
509fd4 |
return echo.NewHTTPError(http.StatusUnsupportedMediaType, "request must be json-encoded")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
err := json.NewDecoder(request.Body).Decode(i)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("cannot parse request body: %v", err))
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return nil
|
|
Packit Service |
509fd4 |
}
|