kojiapi: expose logs on the API

Add an API route that returns logs for a specific compose.

For now, this contains the result of the job, in JSON. The idea is to
put more and more of this information into structured APIs. This is a
first step to make logs available at all.

Amend koji-compose.py to check that the route exist and contains as many
"image_logs" as images that were requested (currently always 1).

Based on a patch by Chloe Kaubisch <chloe.kaubisch@gmail.com>.
This commit is contained in:
Lars Karlitski 2020-11-09 21:32:41 +01:00
parent 263f8d6360
commit e47b44329e
4 changed files with 96 additions and 0 deletions

View file

@ -10,6 +10,11 @@ import (
"net/http"
)
// ComposeLogs defines model for ComposeLogs.
type ComposeLogs struct {
ImageLogs []interface{} `json:"image_logs"`
}
// ComposeRequest defines model for ComposeRequest.
type ComposeRequest struct {
Distribution string `json:"distribution"`
@ -76,6 +81,9 @@ type ServerInterface interface {
// The status of a compose
// (GET /compose/{id})
GetComposeId(ctx echo.Context, id string) error
// Get logs for a compose.
// (GET /compose/{id}/logs)
GetComposeIdLogs(ctx echo.Context, id string) error
// status
// (GET /status)
GetStatus(ctx echo.Context) error
@ -111,6 +119,22 @@ func (w *ServerInterfaceWrapper) GetComposeId(ctx echo.Context) error {
return err
}
// GetComposeIdLogs converts echo context to params.
func (w *ServerInterfaceWrapper) GetComposeIdLogs(ctx echo.Context) error {
var err error
// ------------- Path parameter "id" -------------
var id string
err = runtime.BindStyledParameter("simple", false, "id", ctx.Param("id"), &id)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
}
// Invoke the callback with all the unmarshalled arguments
err = w.Handler.GetComposeIdLogs(ctx, id)
return err
}
// GetStatus converts echo context to params.
func (w *ServerInterfaceWrapper) GetStatus(ctx echo.Context) error {
var err error
@ -144,6 +168,7 @@ func RegisterHandlers(router EchoRouter, si ServerInterface) {
router.POST("/compose", wrapper.PostCompose)
router.GET("/compose/:id", wrapper.GetComposeId)
router.GET("/compose/:id/logs", wrapper.GetComposeIdLogs)
router.GET("/status", wrapper.GetStatus)
}

View file

@ -55,6 +55,38 @@ paths:
text/plain:
schema:
type: string
'/compose/{id}/logs':
get:
summary: Get logs for a compose.
parameters:
- in: path
name: id
schema:
type: string
format: uuid
example: 123e4567-e89b-12d3-a456-426655440000
required: true
description: ID of compose status to get
description: 'Get the status of a running or finished compose. This includes whether or not it succeeded, and also meta information about the result.'
responses:
'200':
description: The logs for the given compose, in no particular format (though valid JSON).
content:
application/json:
schema:
$ref: '#/components/schemas/ComposeLogs'
'400':
description: Invalid compose id
content:
text/plain:
schema:
type: string
'404':
description: Unknown compose id
content:
text/plain:
schema:
type: string
/compose:
post:
summary: Create compose
@ -115,6 +147,12 @@ components:
koji_task_id:
type: integer
example: 203143
ComposeLogs:
required:
- image_logs
properties:
image_logs:
type: array
ImageStatus:
required:
- status

View file

@ -298,6 +298,31 @@ func (h *apiHandlers) GetStatus(ctx echo.Context) error {
})
}
// Get logs for a compose
func (h *apiHandlers) GetComposeIdLogs(ctx echo.Context, idstr string) error {
id, err := uuid.Parse(idstr)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
}
var result worker.OSBuildJobResult
_, err = h.server.workers.JobStatus(id, &result)
if err != nil {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Job %s not found: %s", idstr, err))
}
// Return the OSBuildJobResult as-is for now. The contents of ImageLogs
// is not part of the API. It's meant for a human to be able to access
// the logs, which just happen to be in JSON.
response := api.ComposeLogs{
ImageLogs: []interface{}{
result,
},
}
return ctx.JSON(http.StatusOK, response)
}
// A simple echo.Binder(), which only accepts application/json, but is more
// strict than echo's DefaultBinder. It does not handle binding query
// parameters either.

View file

@ -86,6 +86,14 @@ def main(distro, arch):
time.sleep(10)
r = requests.get(f"https://localhost/api/composer-koji/v1/compose/{compose_id}/logs",
cert=("/etc/osbuild-composer/worker-crt.pem", "/etc/osbuild-composer/worker-key.pem"),
verify="/etc/osbuild-composer/ca-crt.pem")
logs = r.json()
assert "image_logs" in logs
assert type(logs["image_logs"]) == list
assert len(logs["image_logs"]) == len(cr["image_requests"])
if __name__ == "__main__":
if len(sys.argv) != 3: