debian-forge-composer/internal/worker/client.go
Lars Karlitski 3bedd25087 worker/api: send job id to worker after all
Full circle. After switching the worker to not operate on jobs directly,
send the id anyway, so that workers can print it in their logs.
2020-09-11 14:23:24 +01:00

214 lines
4.9 KiB
Go

package worker
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"github.com/google/uuid"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/target"
"github.com/osbuild/osbuild-composer/internal/worker/api"
)
type Client struct {
server *url.URL
requester *http.Client
}
type Job interface {
Id() uuid.UUID
OSBuildArgs() (distro.Manifest, []*target.Target, error)
Update(status common.ImageBuildState, result *osbuild.Result) error
Canceled() (bool, error)
UploadArtifact(name string, reader io.Reader) error
}
type job struct {
requester *http.Client
id uuid.UUID
manifest distro.Manifest
targets []*target.Target
location string
artifactLocation string
}
func NewClient(baseURL string, conf *tls.Config) (*Client, error) {
server, err := url.Parse(baseURL)
if err != nil {
return nil, err
}
requester := &http.Client{
Transport: &http.Transport{
TLSClientConfig: conf,
},
}
return &Client{server, requester}, nil
}
func NewClientUnix(path string) *Client {
server, err := url.Parse("http://localhost")
if err != nil {
panic(err)
}
requester := &http.Client{
Transport: &http.Transport{
DialContext: func(context context.Context, network, addr string) (net.Conn, error) {
return net.Dial("unix", path)
},
},
}
return &Client{server, requester}
}
func (c *Client) RequestJob() (Job, error) {
url, err := c.server.Parse("/jobs")
if err != nil {
// This only happens when "/jobs" cannot be parsed.
panic(err)
}
var buf bytes.Buffer
err = json.NewEncoder(&buf).Encode(api.RequestJobJSONRequestBody{})
if err != nil {
panic(err)
}
response, err := c.requester.Post(url.String(), "application/json", &buf)
if err != nil {
return nil, fmt.Errorf("error requesting job: %v", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusCreated {
var er errorResponse
_ = json.NewDecoder(response.Body).Decode(&er)
return nil, fmt.Errorf("couldn't create job, got %d: %s", response.StatusCode, er.Message)
}
var jr requestJobResponse
err = json.NewDecoder(response.Body).Decode(&jr)
if err != nil {
return nil, fmt.Errorf("error parsing response: %v", err)
}
location, err := c.server.Parse(jr.Location)
if err != nil {
return nil, fmt.Errorf("error parsing location url in response: %v", err)
}
artifactLocation, err := c.server.Parse(jr.ArtifactLocation)
if err != nil {
return nil, fmt.Errorf("error parsing artifact location url in response: %v", err)
}
return &job{
requester: c.requester,
id: jr.Id,
manifest: jr.Manifest,
targets: jr.Targets,
location: location.String(),
artifactLocation: artifactLocation.String(),
}, nil
}
func (j *job) Id() uuid.UUID {
return j.id
}
func (j *job) OSBuildArgs() (distro.Manifest, []*target.Target, error) {
return j.manifest, j.targets, nil
}
func (j *job) Update(status common.ImageBuildState, result *osbuild.Result) error {
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(api.UpdateJobJSONRequestBody{
Result: result,
Status: status.ToString(),
})
if err != nil {
panic(err)
}
req, err := http.NewRequest("PATCH", j.location, &buf)
if err != nil {
panic(err)
}
req.Header.Add("Content-Type", "application/json")
response, err := j.requester.Do(req)
if err != nil {
return fmt.Errorf("error fetching job info: %v", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return errors.New("error setting job status")
}
return nil
}
func (j *job) Canceled() (bool, error) {
response, err := j.requester.Get(j.location)
if err != nil {
return false, fmt.Errorf("error fetching job info: %v", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return false, fmt.Errorf("unexpected return value: %v", response.StatusCode)
}
var jr getJobResponse
err = json.NewDecoder(response.Body).Decode(&jr)
if err != nil {
return false, fmt.Errorf("error parsing reponse: %v", err)
}
return jr.Canceled, nil
}
func (j *job) UploadArtifact(name string, reader io.Reader) error {
if j.artifactLocation == "" {
return fmt.Errorf("server does not accept artifacts for this job")
}
loc, err := url.Parse(j.artifactLocation)
if err != nil {
return fmt.Errorf("error parsing job location: %v", err)
}
loc, err = loc.Parse(url.PathEscape(name))
if err != nil {
panic(err)
}
req, err := http.NewRequest("PUT", loc.String(), reader)
if err != nil {
return fmt.Errorf("cannot create request: %v", err)
}
req.Header.Add("Content-Type", "application/octet-stream")
_, err = j.requester.Do(req)
if err != nil {
return fmt.Errorf("error uploading artifcat: %v", err)
}
return nil
}