debian-forge-composer/cmd/osbuild-composer/main.go
Lars Karlitski b5769add2c store: move queue out of the store
The store is responsible for two things: user state and the compose queue. This
is problematic, because the rcm API has slightly different semantics from weldr
and only used the queue part of the store. Also, the store is simply too
complex.

This commit splits the queue part out, using the new jobqueue package in both
the weldr and the rcm package. The queue is saved to a new directory `queue/`.

The weldr package now also has access to a worker server to enqueue and list
jobs. Its store continues to track composes, but the `QueueStatus` for each
compose (and image build) is deprecated. The field in `ImageBuild` is kept for
backwards compatibility for composes which finished before this change, but a
lot of code dealing with it in package compose is dropped.

store.PushCompose() is degraded to storing a new compose. It should probably be
renamed in the future. store.PopJob() is removed.

Job ids are now independent of compose ids. Because of that, the local
target gains ComposeId and ImageBuildId fields, because a worker cannot
infer those from a job anymore. This also necessitates a change in the
worker API: the job routes are changed to expect that instead of a
(compose id, image build id) pair. The route that accepts built images
keeps that pair, because it reports the image back to weldr.

worker.Server() now interacts with a job queue instead of the store. It gains
public functions that allow enqueuing an osbuild job and getting its status,
because only it knows about the specific argument and result types in the job
queue (OSBuildJob and OSBuildJobResult). One oddity remains: it needs to report
an uploaded image to weldr. Do this with a function that's passed in for now,
so that the dependency to the store can be dropped completely.

The rcm API drops its dependencies to package blueprint and store, because it
too interacts only with the worker server now.

Fixes #342
2020-05-08 14:53:00 +02:00

188 lines
5.4 KiB
Go

package main
import (
"crypto/tls"
"crypto/x509"
"flag"
"io/ioutil"
"log"
"os"
"path"
"github.com/osbuild/osbuild-composer/internal/distro/fedora30"
"github.com/osbuild/osbuild-composer/internal/distro/fedora31"
"github.com/osbuild/osbuild-composer/internal/distro/fedora32"
"github.com/osbuild/osbuild-composer/internal/distro/rhel81"
"github.com/osbuild/osbuild-composer/internal/distro/rhel82"
"github.com/osbuild/osbuild-composer/internal/distro/rhel83"
"github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue"
"github.com/osbuild/osbuild-composer/internal/rcm"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/store"
"github.com/osbuild/osbuild-composer/internal/weldr"
"github.com/osbuild/osbuild-composer/internal/worker"
"github.com/coreos/go-systemd/activation"
)
type connectionConfig struct {
CACertFile string
ServerKeyFile string
ServerCertFile string
}
func createTLSConfig(c *connectionConfig) (*tls.Config, error) {
caCertPEM, err := ioutil.ReadFile(c.CACertFile)
if err != nil {
return nil, err
}
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM(caCertPEM)
if !ok {
panic("failed to parse root certificate")
}
cert, err := tls.LoadX509KeyPair(c.ServerCertFile, c.ServerKeyFile)
if err != nil {
return nil, err
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: roots,
}, nil
}
func main() {
var verbose bool
flag.BoolVar(&verbose, "v", false, "Print access log")
flag.Parse()
stateDir, ok := os.LookupEnv("STATE_DIRECTORY")
if !ok {
log.Fatal("STATE_DIRECTORY is not set. Is the service file missing StateDirectory=?")
}
listeners, err := activation.ListenersWithNames()
if err != nil {
log.Fatalf("Could not get listening sockets: " + err.Error())
}
if _, exists := listeners["osbuild-composer.socket"]; !exists {
log.Fatalf("osbuild-composer.socket doesn't exist")
}
composerListeners := listeners["osbuild-composer.socket"]
if len(composerListeners) != 2 && len(composerListeners) != 3 {
log.Fatalf("Unexpected number of listening sockets (%d), expected 2 or 3", len(composerListeners))
}
weldrListener := composerListeners[0]
jobListener := composerListeners[1]
cacheDirectory, ok := os.LookupEnv("CACHE_DIRECTORY")
if !ok {
log.Fatal("CACHE_DIRECTORY is not set. Is the service file missing CacheDirectory=?")
}
rpm := rpmmd.NewRPMMD(path.Join(cacheDirectory, "rpmmd"))
distros, err := distro.NewRegistry(fedora30.New(), fedora31.New(), fedora32.New(), rhel81.New(), rhel82.New(), rhel83.New())
if err != nil {
log.Fatalf("Error loading distros: %v", err)
}
distribution, err := distros.FromHost()
if err != nil {
log.Fatalf("Could not determine distro from host: " + err.Error())
}
arch, err := distribution.GetArch(common.CurrentArch())
if err != nil {
log.Fatalf("Host distro does not support host architecture: " + err.Error())
}
repoMap, err := rpmmd.LoadRepositories([]string{"/etc/osbuild-composer", "/usr/share/osbuild-composer"}, distribution.Name())
if err != nil {
log.Fatalf("Could not load repositories for %s: %v", distribution.Name(), err)
}
var logger *log.Logger
if verbose {
logger = log.New(os.Stdout, "", 0)
}
store := store.New(&stateDir)
queueDir := path.Join(stateDir, "jobs")
err = os.Mkdir(queueDir, 0700)
if err != nil && !os.IsExist(err) {
log.Fatalf("cannot create queue directory: %v", err)
}
jobs, err := fsjobqueue.New(queueDir)
if err != nil {
log.Fatalf("cannot create jobqueue: %v", err)
}
outputDir := path.Join(stateDir, "outputs")
err = os.Mkdir(outputDir, 0755)
if err != nil && !os.IsExist(err) {
log.Fatalf("cannot create output directory: %v", err)
}
workers := worker.NewServer(logger, jobs, store.AddImageToImageUpload)
weldrAPI := weldr.New(rpm, arch, distribution, repoMap[common.CurrentArch()], logger, store, workers)
go func() {
err := workers.Serve(jobListener)
common.PanicOnError(err)
}()
// Optionally run RCM API as well as Weldr API
if rcmApiListeners, exists := listeners["osbuild-rcm.socket"]; exists {
if len(rcmApiListeners) != 1 {
// Use Fatal to call os.Exit with non-zero return value
log.Fatal("The RCM API socket unit is misconfigured. It should contain only one socket.")
}
rcmListener := rcmApiListeners[0]
rcmAPI := rcm.New(logger, workers, rpm, distros)
go func() {
err := rcmAPI.Serve(rcmListener)
// If the RCM API fails, take down the whole process, not just a single gorutine
log.Fatal("RCM API failed: ", err)
}()
}
if remoteWorkerListeners, exists := listeners["osbuild-remote-worker.socket"]; exists {
for _, listener := range remoteWorkerListeners {
log.Printf("Starting remote listener\n")
tlsConfig, err := createTLSConfig(&connectionConfig{
CACertFile: "/etc/osbuild-composer/ca-crt.pem",
ServerKeyFile: "/etc/osbuild-composer/composer-key.pem",
ServerCertFile: "/etc/osbuild-composer/composer-crt.pem",
})
if err != nil {
log.Fatalf("TLS configuration cannot be created: " + err.Error())
}
listener := tls.NewListener(listener, tlsConfig)
go func() {
err := workers.Serve(listener)
common.PanicOnError(err)
}()
}
}
err = weldrAPI.Serve(weldrListener)
common.PanicOnError(err)
}