A job's purpose is to build an osbuild manifest and upload the results somewhere. It should not know about which distro was used to generate the pipeline. Workers depended on the distro package in two ways: 1. To set an osbuild `--build-env`. This is not necessary anymore in new versions of osbuild. More importantly, it was wrong: it passed the runner from the distro that is being built, instead of one that matches the host. This patch simply removes that logic. 2. To fetch the output filename with `Distro.FilenameFromType()`. While that is useful, I don't think it warrants the dependency. This patch uses the fact that all current pipelines output exactly one file and uploads that. This should probably be extended in the future to upload all output files, or to name them explicitly in the upload target. The worker should now compile to a smaller binary and do less unnecessary work on startup (like reading repository files).
163 lines
3.7 KiB
Go
163 lines
3.7 KiB
Go
package jobqueue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
|
|
"github.com/osbuild/osbuild-composer/internal/osbuild"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/osbuild/osbuild-composer/internal/common"
|
|
"github.com/osbuild/osbuild-composer/internal/target"
|
|
"github.com/osbuild/osbuild-composer/internal/upload/awsupload"
|
|
)
|
|
|
|
type Job struct {
|
|
ID uuid.UUID `json:"id"`
|
|
ImageBuildID int `json:"image_build_id"`
|
|
Manifest *osbuild.Manifest `json:"manifest"`
|
|
Targets []*target.Target `json:"targets"`
|
|
OutputType string `json:"output_type"`
|
|
}
|
|
|
|
type JobStatus struct {
|
|
Status common.ImageBuildState `json:"status"`
|
|
Result *common.ComposeResult `json:"result"`
|
|
}
|
|
|
|
type TargetsError struct {
|
|
Errors []error
|
|
}
|
|
|
|
func (e *TargetsError) Error() string {
|
|
errString := fmt.Sprintf("%d target(s) errored:\n", len(e.Errors))
|
|
|
|
for _, err := range e.Errors {
|
|
errString += err.Error() + "\n"
|
|
}
|
|
|
|
return errString
|
|
}
|
|
|
|
func (job *Job) Run(uploader LocalTargetUploader) (*common.ComposeResult, error) {
|
|
tmpStore, err := ioutil.TempDir("/var/tmp", "osbuild-store")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error setting up osbuild store: %v", err)
|
|
}
|
|
// FIXME: how to handle errors in defer?
|
|
defer os.RemoveAll(tmpStore)
|
|
|
|
cmd := exec.Command(
|
|
"osbuild",
|
|
"--store", tmpStore,
|
|
"--json", "-",
|
|
)
|
|
cmd.Stderr = os.Stderr
|
|
|
|
stdin, err := cmd.StdinPipe()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error setting up stdin for osbuild: %v", err)
|
|
}
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error setting up stdout for osbuild: %v", err)
|
|
}
|
|
|
|
err = cmd.Start()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error starting osbuild: %v", err)
|
|
}
|
|
|
|
err = json.NewEncoder(stdin).Encode(job.Manifest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error encoding osbuild pipeline: %v", err)
|
|
}
|
|
// FIXME: handle or comment this possible error
|
|
_ = stdin.Close()
|
|
|
|
var result common.ComposeResult
|
|
err = json.NewDecoder(stdout).Decode(&result)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error decoding osbuild output: %#v", err)
|
|
}
|
|
|
|
err = cmd.Wait()
|
|
if err != nil {
|
|
return &result, err
|
|
}
|
|
|
|
var r []error
|
|
|
|
for _, t := range job.Targets {
|
|
switch options := t.Options.(type) {
|
|
case *target.LocalTargetOptions:
|
|
outputDir := path.Join(tmpStore, "refs", result.OutputID)
|
|
|
|
files, err := ioutil.ReadDir(outputDir)
|
|
if err != nil {
|
|
r = append(r, err)
|
|
continue
|
|
}
|
|
|
|
// TODO osbuild pipelines can have multiple outputs. All the pipelines we
|
|
// are currently generating have exactly one, but we should support
|
|
// uploading all results in the future.
|
|
if len(files) != 1 {
|
|
r = append(r, fmt.Errorf("expected exactly one resulting image file"))
|
|
continue
|
|
}
|
|
|
|
f, err := os.Open(path.Join(outputDir, files[0].Name()))
|
|
if err != nil {
|
|
r = append(r, err)
|
|
continue
|
|
}
|
|
|
|
err = uploader.UploadImage(job, f)
|
|
if err != nil {
|
|
r = append(r, err)
|
|
continue
|
|
}
|
|
|
|
case *target.AWSTargetOptions:
|
|
|
|
a, err := awsupload.New(options.Region, options.AccessKeyID, options.SecretAccessKey)
|
|
if err != nil {
|
|
r = append(r, err)
|
|
continue
|
|
}
|
|
|
|
if options.Key == "" {
|
|
options.Key = job.ID.String()
|
|
}
|
|
|
|
_, err = a.Upload(tmpStore+"/refs/"+result.OutputID+"/image.raw.xz", options.Bucket, options.Key)
|
|
if err != nil {
|
|
r = append(r, err)
|
|
continue
|
|
}
|
|
|
|
/* TODO: communicate back the AMI */
|
|
_, err = a.Register(t.ImageName, options.Bucket, options.Key)
|
|
if err != nil {
|
|
r = append(r, err)
|
|
continue
|
|
}
|
|
case *target.AzureTargetOptions:
|
|
default:
|
|
r = append(r, fmt.Errorf("invalid target type"))
|
|
}
|
|
}
|
|
|
|
if len(r) > 0 {
|
|
return &result, &TargetsError{r}
|
|
}
|
|
|
|
return &result, nil
|
|
}
|