Blame internal/kojiapi/server.go

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
}