debian-forge-composer/cmd/osbuild-composer/main.go
Tom Gundersen 6bab73f378 kojiapi: move from chi to echo
Follow the worker API so we standardise on one library. This simplifies
the code quite a bit.

No functional change.

Signed-off-by: Tom Gundersen <teg@jklm.no>
2020-09-21 11:18:45 +01:00

240 lines
6.8 KiB
Go

package main
import (
"crypto/tls"
"crypto/x509"
"flag"
"io/ioutil"
"log"
"os"
"path"
"github.com/BurntSushi/toml"
"github.com/osbuild/osbuild-composer/internal/distro/fedora31"
"github.com/osbuild/osbuild-composer/internal/distro/fedora32"
"github.com/osbuild/osbuild-composer/internal/distro/rhel8"
"github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue"
"github.com/osbuild/osbuild-composer/internal/kojiapi"
"github.com/osbuild/osbuild-composer/internal/upload/koji"
"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"
)
const configFile = "/etc/osbuild-composer/osbuild-composer.toml"
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 config struct {
KojiServers map[string]struct {
Kerberos *struct {
Principal string `toml:"principal"`
KeyTab string `toml:"keytab"`
} `toml:"kerberos,omitempty"`
} `toml:"koji"`
}
var verbose bool
flag.BoolVar(&verbose, "v", false, "Print access log")
flag.Parse()
_, err := toml.DecodeFile(configFile, &config)
if err == nil {
log.Println("Composer configuration:")
encoder := toml.NewEncoder(log.Writer())
err := encoder.Encode(&config)
if err != nil {
log.Fatalf("Could not print config: %v", err)
}
} else if !os.IsNotExist(err) {
log.Fatalf("Could not load config file '%s': %v", configFile, err)
}
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"), "/usr/libexec/osbuild-composer/dnf-json")
distros, err := distro.NewRegistry(fedora31.New(), fedora32.New(), rhel8.New())
if err != nil {
log.Fatalf("Error loading distros: %v", err)
}
distribution, beta, 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())
}
// TODO: refactor to be more generic
name := distribution.Name()
if beta {
name += "-beta"
}
repoMap, err := rpmmd.LoadRepositories([]string{"/etc/osbuild-composer", "/usr/share/osbuild-composer"}, 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, arch, logger)
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, []string{"osbuild"})
if err != nil {
log.Fatalf("cannot create jobqueue: %v", err)
}
artifactsDir := path.Join(stateDir, "artifacts")
err = os.Mkdir(artifactsDir, 0755)
if err != nil && !os.IsExist(err) {
log.Fatalf("cannot create artifacts directory: %v", err)
}
compatOutputDir := path.Join(stateDir, "outputs")
workers := worker.NewServer(logger, jobs, artifactsDir)
weldrAPI := weldr.New(rpm, arch, distribution, repoMap[common.CurrentArch()], logger, store, workers, compatOutputDir)
go func() {
err := workers.Serve(jobListener)
common.PanicOnError(err)
}()
// Optionally run Koji API
if kojiListeners, exists := listeners["osbuild-composer-koji.socket"]; exists {
kojiServers := make(map[string]koji.GSSAPICredentials)
for server, creds := range config.KojiServers {
if creds.Kerberos == nil {
// For now we only support Kerberos authentication.
continue
}
kojiServers[server] = koji.GSSAPICredentials{
Principal: creds.Kerberos.Principal,
KeyTab: creds.Kerberos.KeyTab,
}
}
kojiServer := kojiapi.NewServer(logger, workers, rpm, distros, kojiServers)
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())
}
if len(kojiListeners) != 1 {
// Use Fatal to call os.Exit with non-zero return value
log.Fatal("The osbuild-composer-koji.socket unit is misconfigured. It should contain only one socket.")
}
kojiListener := tls.NewListener(kojiListeners[0], tlsConfig)
go func() {
err = kojiServer.Serve(kojiListener)
// If the koji server fails, take down the whole process, not just a single goroutine
log.Fatal("osbuild-composer-koji.socket 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)
}