This will make counting successful image builds in Splunk and Cloudwatch much simpler and robust.
178 lines
5.3 KiB
Go
178 lines
5.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
|
|
"github.com/osbuild/osbuild-composer/internal/common"
|
|
"github.com/osbuild/osbuild-composer/internal/distro"
|
|
"github.com/osbuild/osbuild-composer/internal/upload/koji"
|
|
"github.com/osbuild/osbuild-composer/internal/worker"
|
|
"github.com/osbuild/osbuild-composer/internal/worker/clienterrors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type OSBuildKojiJobImpl struct {
|
|
Store string
|
|
Output string
|
|
KojiServers map[string]koji.GSSAPICredentials
|
|
relaxTimeoutFactor uint
|
|
}
|
|
|
|
func (impl *OSBuildKojiJobImpl) kojiUpload(file *os.File, server, directory, filename string) (string, uint64, error) {
|
|
transport := koji.CreateKojiTransport(impl.relaxTimeoutFactor)
|
|
|
|
serverURL, err := url.Parse(server)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
|
|
creds, exists := impl.KojiServers[serverURL.Hostname()]
|
|
if !exists {
|
|
return "", 0, fmt.Errorf("Koji server has not been configured: %s", serverURL.Hostname())
|
|
}
|
|
|
|
k, err := koji.NewFromGSSAPI(server, &creds, transport)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
defer func() {
|
|
err := k.Logout()
|
|
if err != nil {
|
|
logrus.Warnf("koji logout failed: %v", err)
|
|
}
|
|
}()
|
|
|
|
return k.Upload(file, directory, filename)
|
|
}
|
|
|
|
func validateKojiResult(result *worker.OSBuildKojiJobResult, jobID string) {
|
|
logWithId := logrus.WithField("jobId", jobID)
|
|
if result.JobError != nil {
|
|
logWithId.Errorf("osbuild job failed: %s", result.JobError.Reason)
|
|
return
|
|
}
|
|
// if the job failed, but the JobError is
|
|
// nil, we still need to handle this as an error
|
|
if result.OSBuildOutput == nil || !result.OSBuildOutput.Success {
|
|
reason := "osbuild job was unsuccessful"
|
|
logWithId.Errorf("osbuild job failed: %s", reason)
|
|
result.JobError = clienterrors.WorkerClientError(clienterrors.ErrorBuildJob, reason)
|
|
} else {
|
|
logWithId.Infof("osbuild-koji job succeeded")
|
|
}
|
|
}
|
|
|
|
func (impl *OSBuildKojiJobImpl) Run(job worker.Job) error {
|
|
var result worker.OSBuildKojiJobResult
|
|
outputDirectory, err := ioutil.TempDir(impl.Output, job.Id().String()+"-*")
|
|
if err != nil {
|
|
return fmt.Errorf("error creating temporary output directory: %v", err)
|
|
}
|
|
defer func() {
|
|
validateKojiResult(&result, job.Id().String())
|
|
|
|
// this is necessary for early return errors
|
|
err = job.Update(&result)
|
|
if err != nil {
|
|
logrus.Warnf("Error reporting job result: %v", err)
|
|
}
|
|
|
|
err := os.RemoveAll(outputDirectory)
|
|
if err != nil {
|
|
logrus.Warnf("Error removing temporary output directory (%s): %v", outputDirectory, err)
|
|
}
|
|
}()
|
|
|
|
var args worker.OSBuildKojiJob
|
|
err = job.Args(&args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var initArgs worker.KojiInitJobResult
|
|
err = job.DynamicArgs(0, &initArgs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
result.Arch = common.CurrentArch()
|
|
result.HostOS, err = distro.GetRedHatRelease()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// In case the manifest is empty, try to get it from dynamic args
|
|
if len(args.Manifest) == 0 {
|
|
if job.NDynamicArgs() > 1 {
|
|
var manifestJR worker.ManifestJobByIDResult
|
|
err = job.DynamicArgs(1, &manifestJR)
|
|
if err != nil {
|
|
result.JobError = clienterrors.WorkerClientError(clienterrors.ErrorParsingDynamicArgs, "Error parsing dynamic args")
|
|
return err
|
|
}
|
|
|
|
// skip the job if the manifest generation failed
|
|
if manifestJR.JobError != nil {
|
|
result.JobError = clienterrors.WorkerClientError(clienterrors.ErrorManifestDependency, "Manifest dependency failed")
|
|
return nil
|
|
}
|
|
args.Manifest = manifestJR.Manifest
|
|
if len(args.Manifest) == 0 {
|
|
err := fmt.Errorf("Received empty manifest")
|
|
result.JobError = clienterrors.WorkerClientError(clienterrors.ErrorManifestDependency, err.Error())
|
|
return err
|
|
}
|
|
} else {
|
|
err := fmt.Errorf("Job has no manifest")
|
|
result.JobError = clienterrors.WorkerClientError(clienterrors.ErrorManifestDependency, err.Error())
|
|
return err
|
|
}
|
|
}
|
|
|
|
if initArgs.JobError == nil {
|
|
exports := args.Exports
|
|
if len(exports) == 0 {
|
|
// job did not define exports, likely coming from an older version of composer
|
|
// fall back to default "assembler"
|
|
exports = []string{"assembler"}
|
|
} else if len(exports) > 1 {
|
|
// this worker only supports returning one (1) export
|
|
err = fmt.Errorf("at most one build artifact can be exported")
|
|
result.JobError = clienterrors.WorkerClientError(clienterrors.ErrorBuildJob, err.Error())
|
|
return err
|
|
}
|
|
result.OSBuildOutput, err = RunOSBuild(args.Manifest, impl.Store, outputDirectory, exports, os.Stderr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// NOTE: Currently OSBuild supports multiple exports, but this isn't used
|
|
// by any of the image types and it can't be specified during the request.
|
|
// Use the first (and presumably only) export for the imagePath.
|
|
exportPath := exports[0]
|
|
if result.OSBuildOutput.Success {
|
|
f, err := os.Open(path.Join(outputDirectory, exportPath, args.ImageName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result.ImageHash, result.ImageSize, err = impl.kojiUpload(f, args.KojiServer, args.KojiDirectory, args.KojiFilename)
|
|
if err != nil {
|
|
result.JobError = clienterrors.WorkerClientError(clienterrors.ErrorKojiBuild, err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy pipeline info to the result
|
|
result.PipelineNames = args.PipelineNames
|
|
|
|
err = job.Update(&result)
|
|
if err != nil {
|
|
return fmt.Errorf("Error reporting job result: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|