worker/client: expose server errors

The worker API returns errors of the form:

  { "message": "..." }

Print the message of those errors when receiving an error on the client.

This adds an `Error` type to openapi.yml and marks all routes as
returning it on 4XX and 5XX.
This commit is contained in:
Lars Karlitski 2020-09-09 10:23:08 +02:00 committed by Tom Gundersen
parent 3bedd25087
commit ca35f25fcf
4 changed files with 80 additions and 13 deletions

View file

@ -10,6 +10,11 @@ import (
"net/http" "net/http"
) )
// Error defines model for Error.
type Error struct {
Message string `json:"message"`
}
// RequestJobJSONBody defines parameters for RequestJob. // RequestJobJSONBody defines parameters for RequestJob.
type RequestJobJSONBody map[string]interface{} type RequestJobJSONBody map[string]interface{}

View file

@ -23,6 +23,17 @@ paths:
- OK - OK
required: required:
- status - status
4XX:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
5XX:
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
operationId: GetStatus operationId: GetStatus
description: Simple status handler to check whether the service is up. description: Simple status handler to check whether the service is up.
/jobs: /jobs:
@ -53,6 +64,17 @@ paths:
- manifest - manifest
- location - location
- id - id
4XX:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
5XX:
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
operationId: RequestJob operationId: RequestJob
requestBody: requestBody:
content: content:
@ -85,6 +107,18 @@ paths:
type: boolean type: boolean
required: required:
- canceled - canceled
4XX:
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
5XX:
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
operationId: GetJob operationId: GetJob
description: '' description: ''
patch: patch:
@ -127,6 +161,17 @@ paths:
responses: responses:
'200': '200':
description: OK description: OK
4XX:
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
5XX:
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
operationId: UploadJobArtifact operationId: UploadJobArtifact
requestBody: requestBody:
content: content:
@ -134,4 +179,12 @@ paths:
schema: schema:
type: string type: string
components: components:
schemas: {} schemas:
Error:
title: Error
type: object
properties:
message:
type: string
required:
- message

View file

@ -5,7 +5,6 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net" "net"
@ -94,9 +93,7 @@ func (c *Client) RequestJob() (Job, error) {
defer response.Body.Close() defer response.Body.Close()
if response.StatusCode != http.StatusCreated { if response.StatusCode != http.StatusCreated {
var er errorResponse return nil, errorFromResponse(response, "error requesting job")
_ = json.NewDecoder(response.Body).Decode(&er)
return nil, fmt.Errorf("couldn't create job, got %d: %s", response.StatusCode, er.Message)
} }
var jr requestJobResponse var jr requestJobResponse
@ -157,7 +154,7 @@ func (j *job) Update(status common.ImageBuildState, result *osbuild.Result) erro
defer response.Body.Close() defer response.Body.Close()
if response.StatusCode != http.StatusOK { if response.StatusCode != http.StatusOK {
return errors.New("error setting job status") return errorFromResponse(response, "error setting job status")
} }
return nil return nil
@ -171,7 +168,7 @@ func (j *job) Canceled() (bool, error) {
defer response.Body.Close() defer response.Body.Close()
if response.StatusCode != http.StatusOK { if response.StatusCode != http.StatusOK {
return false, fmt.Errorf("unexpected return value: %v", response.StatusCode) return false, errorFromResponse(response, "error fetching job info")
} }
var jr getJobResponse var jr getJobResponse
@ -205,10 +202,26 @@ func (j *job) UploadArtifact(name string, reader io.Reader) error {
req.Header.Add("Content-Type", "application/octet-stream") req.Header.Add("Content-Type", "application/octet-stream")
_, err = j.requester.Do(req) response, err := j.requester.Do(req)
if err != nil { if err != nil {
return fmt.Errorf("error uploading artifcat: %v", err) return fmt.Errorf("error uploading artifact: %v", err)
}
if response.StatusCode != 200 {
return errorFromResponse(response, "error uploading artifact")
} }
return nil return nil
} }
// Parses an api.Error from a response and returns it as a golang error. Other
// errors, such failing to parse the response, are returned as golang error as
// well. If client code expects an error, it gets one.
func errorFromResponse(response *http.Response, message string) error {
var e api.Error
err := json.NewDecoder(response.Body).Decode(&e)
if err != nil {
return fmt.Errorf("failed to parse error response: %v", err)
}
return fmt.Errorf("%v: %v — %v", message, response.StatusCode, e.Message)
}

View file

@ -29,10 +29,6 @@ type statusResponse struct {
Status string `json:"status"` Status string `json:"status"`
} }
type errorResponse struct {
Message string `json:"message"`
}
type requestJobResponse struct { type requestJobResponse struct {
Id uuid.UUID `json:"id"` Id uuid.UUID `json:"id"`
Manifest distro.Manifest `json:"manifest"` Manifest distro.Manifest `json:"manifest"`