many: remove jobsite code
This was replaced by `cmd/osbuild-worker-executor`.
This commit is contained in:
parent
f6c1709d8d
commit
7dea1bcd01
6 changed files with 1 additions and 975 deletions
2
Makefile
2
Makefile
|
|
@ -149,8 +149,6 @@ build: $(BUILDDIR)/bin/ build-maintenance
|
||||||
go build -o $<osbuild-upload-oci ./cmd/osbuild-upload-oci/
|
go build -o $<osbuild-upload-oci ./cmd/osbuild-upload-oci/
|
||||||
go build -o $<osbuild-upload-generic-s3 ./cmd/osbuild-upload-generic-s3/
|
go build -o $<osbuild-upload-generic-s3 ./cmd/osbuild-upload-generic-s3/
|
||||||
go build -o $<osbuild-mock-openid-provider ./cmd/osbuild-mock-openid-provider
|
go build -o $<osbuild-mock-openid-provider ./cmd/osbuild-mock-openid-provider
|
||||||
go build -o $<osbuild-jobsite-manager ./cmd/osbuild-jobsite-manager
|
|
||||||
go build -o $<osbuild-jobsite-builder ./cmd/osbuild-jobsite-builder
|
|
||||||
# also build the test binaries
|
# also build the test binaries
|
||||||
go test -c -tags=integration -o $<osbuild-composer-cli-tests ./cmd/osbuild-composer-cli-tests/main_test.go
|
go test -c -tags=integration -o $<osbuild-composer-cli-tests ./cmd/osbuild-composer-cli-tests/main_test.go
|
||||||
go test -c -tags=integration -o $<osbuild-weldr-tests ./internal/client/
|
go test -c -tags=integration -o $<osbuild-weldr-tests ./internal/client/
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,6 @@ osbuild-worker-executor: The binary that runs osbuild to build an image on an is
|
||||||
|
|
||||||
osbuild-koji: Submits builds to Koji.
|
osbuild-koji: Submits builds to Koji.
|
||||||
|
|
||||||
osbuild-jobsite-*: Provides an isolated set of services to run builds in. They can be separated
|
|
||||||
from the rest of the system through network segmentation or other means. The jobsite and what's
|
|
||||||
in it are only available to things inside it.
|
|
||||||
|
|
||||||
Service binaries
|
Service binaries
|
||||||
================
|
================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,479 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ExitOk int = iota
|
|
||||||
ExitError
|
|
||||||
)
|
|
||||||
|
|
||||||
type State int
|
|
||||||
|
|
||||||
type Handler func(w http.ResponseWriter, r *http.Request) error
|
|
||||||
|
|
||||||
const (
|
|
||||||
StateClaim State = iota
|
|
||||||
StateProvision
|
|
||||||
StatePopulate
|
|
||||||
StateBuild
|
|
||||||
StateProgress
|
|
||||||
StateExport
|
|
||||||
StateDone
|
|
||||||
|
|
||||||
StateError
|
|
||||||
StateSignal
|
|
||||||
StateTimeout
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
argJSON bool
|
|
||||||
|
|
||||||
argBuilderHost string
|
|
||||||
argBuilderPort int
|
|
||||||
|
|
||||||
argTimeoutClaim int
|
|
||||||
argTimeoutProvision int
|
|
||||||
argTimeoutPopulate int
|
|
||||||
argTimeoutBuild int
|
|
||||||
argTimeoutExport int
|
|
||||||
|
|
||||||
argBuildPath string
|
|
||||||
)
|
|
||||||
|
|
||||||
type BuildRequest struct {
|
|
||||||
Pipelines []string `json:"pipelines"`
|
|
||||||
Environments []string `json:"environments"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.BoolVar(&argJSON, "json", false, "Enable JSON output")
|
|
||||||
|
|
||||||
flag.StringVar(&argBuilderHost, "builder-host", "localhost", "Hostname or IP where this program will listen on.")
|
|
||||||
flag.IntVar(&argBuilderPort, "builder-port", 3333, "Port this program will listen on.")
|
|
||||||
|
|
||||||
flag.IntVar(&argTimeoutClaim, "timeout-claim", 600, "Timeout before the claim phase needs to be completed in seconds.")
|
|
||||||
flag.IntVar(&argTimeoutProvision, "timeout-provision", 30, "Timeout before the provision phase needs to be completed in seconds.")
|
|
||||||
flag.IntVar(&argTimeoutPopulate, "timeout-populate", 300, "Timeout before the populate phase needs to be completed in seconds.")
|
|
||||||
flag.IntVar(&argTimeoutBuild, "timeout-build", 3600, "Timeout before the build phase needs to be completed in seconds.")
|
|
||||||
flag.IntVar(&argTimeoutExport, "timeout-export", 1800, "Timeout before the export phase needs to be completed in seconds.")
|
|
||||||
|
|
||||||
flag.StringVar(&argBuildPath, "build-path", "/run/osbuild", "Path to use as a build directory.")
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
opts := &slog.HandlerOptions{Level: slog.LevelInfo}
|
|
||||||
if argJSON {
|
|
||||||
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, opts)))
|
|
||||||
} else {
|
|
||||||
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, opts)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Builder struct {
|
|
||||||
Host string
|
|
||||||
Port int
|
|
||||||
State State
|
|
||||||
StateLock sync.Mutex
|
|
||||||
StateChannel chan State
|
|
||||||
Build *BackgroundProcess
|
|
||||||
|
|
||||||
net *http.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
type BackgroundProcess struct {
|
|
||||||
Process *exec.Cmd
|
|
||||||
Stdout *bytes.Buffer
|
|
||||||
Stderr io.ReadCloser
|
|
||||||
Done bool
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (builder *Builder) SetState(state State) {
|
|
||||||
builder.StateLock.Lock()
|
|
||||||
defer builder.StateLock.Unlock()
|
|
||||||
|
|
||||||
if state <= builder.State {
|
|
||||||
builder.State = StateError
|
|
||||||
} else {
|
|
||||||
builder.State = state
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.StateChannel <- builder.State
|
|
||||||
}
|
|
||||||
|
|
||||||
func (builder *Builder) GetState() State {
|
|
||||||
builder.StateLock.Lock()
|
|
||||||
defer builder.StateLock.Unlock()
|
|
||||||
|
|
||||||
return builder.State
|
|
||||||
}
|
|
||||||
|
|
||||||
func (builder *Builder) GuardState(stateWanted State) {
|
|
||||||
if stateCurrent := builder.GetState(); stateWanted != stateCurrent {
|
|
||||||
slog.Error("Builder.GuardState: Guard state mismatch", "requested", stateWanted, "current", stateCurrent)
|
|
||||||
os.Exit(ExitError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (builder *Builder) RegisterHandler(h Handler) http.HandlerFunc {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if err := h(w, r); err != nil {
|
|
||||||
slog.Error("Handler: error", "error", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (builder *Builder) HandleClaim(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
builder.GuardState(StateClaim)
|
|
||||||
|
|
||||||
if r.Method != "POST" {
|
|
||||||
slog.Error("Builder.HandleClaim: unexpected request method", "method", r.Method)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "%s", "done")
|
|
||||||
|
|
||||||
slog.Info("Builder.HandleClaim: Done")
|
|
||||||
|
|
||||||
builder.SetState(StateProvision)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (builder *Builder) HandleProvision(w http.ResponseWriter, r *http.Request) (err error) {
|
|
||||||
builder.GuardState(StateProvision)
|
|
||||||
|
|
||||||
if r.Method != "PUT" {
|
|
||||||
return fmt.Errorf("Builder.HandleProvision: Unexpected request method")
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Debug("Builder.HandleProvision: Opening manifest.json", "argBuildPath", argBuildPath)
|
|
||||||
|
|
||||||
dst, err := os.OpenFile(
|
|
||||||
path.Join(argBuildPath, "manifest.json"),
|
|
||||||
os.O_WRONLY|os.O_CREATE|os.O_EXCL,
|
|
||||||
0400,
|
|
||||||
)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if cerr := dst.Close(); cerr != nil {
|
|
||||||
err = cerr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Builder.HandleProvision: Failed to open manifest.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Debug("Builder.HandleProvision: Writing manifest.json")
|
|
||||||
|
|
||||||
_, err = io.Copy(dst, r.Body)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Builder.HandleProvision: Failed to write manifest.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
|
|
||||||
if _, err := w.Write([]byte(`done`)); err != nil {
|
|
||||||
return fmt.Errorf("Builder.HandleProvision: Failed to write response")
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("Builder.HandleProvision: Done")
|
|
||||||
|
|
||||||
builder.SetState(StatePopulate)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (builder *Builder) HandlePopulate(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
builder.GuardState(StatePopulate)
|
|
||||||
|
|
||||||
if r.Method != "POST" {
|
|
||||||
return fmt.Errorf("Builder.HandlePopulate: unexpected request method")
|
|
||||||
}
|
|
||||||
storePath := path.Join(argBuildPath, "store")
|
|
||||||
err := os.Mkdir(storePath, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Builder.HandlePopulate: failed to make store directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tarReader := tar.NewReader(r.Body)
|
|
||||||
for header, err := tarReader.Next(); err != io.EOF; header, err = tarReader.Next() {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Builder.HandlerPopulate: failed to unpack sources: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// gosec seems overly zealous here, as the destination gets verified
|
|
||||||
dest := filepath.Join(storePath, header.Name) // #nosec G305
|
|
||||||
if !strings.HasPrefix(dest, filepath.Clean(storePath)) {
|
|
||||||
return fmt.Errorf("Builder.HandlerPopulate: name not clean: %v doesn't have %v prefix", dest, filepath.Clean(storePath))
|
|
||||||
}
|
|
||||||
|
|
||||||
switch header.Typeflag {
|
|
||||||
case tar.TypeDir:
|
|
||||||
if err := os.Mkdir(dest, header.FileInfo().Mode()); err != nil {
|
|
||||||
return fmt.Errorf("Builder.HandlerPopulate: unable to make dir in sources: %v", err)
|
|
||||||
}
|
|
||||||
case tar.TypeReg:
|
|
||||||
file, err := os.Create(dest)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Builder.HandlerPopulate: unable to open file in sources: %v", err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// the inputs are trusted so ignore G110
|
|
||||||
_, err = io.Copy(file, tarReader) // #nosec G110
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Builder.HandlerPopulate: unable to write file in sources: %v", err)
|
|
||||||
}
|
|
||||||
file.Close()
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Builder.HandlerPopulate: unexpected tar header type: %v", header.Typeflag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
if _, err := w.Write([]byte(`done`)); err != nil {
|
|
||||||
return fmt.Errorf("Builder.HandlePopulate: Failed to write response")
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("Builder.HandlePopulate: Done")
|
|
||||||
|
|
||||||
builder.SetState(StateBuild)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (builder *Builder) HandleBuild(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
builder.GuardState(StateBuild)
|
|
||||||
|
|
||||||
if r.Method != "POST" {
|
|
||||||
return fmt.Errorf("Builder.HandleBuild: Unexpected request method")
|
|
||||||
}
|
|
||||||
|
|
||||||
var buildRequest BuildRequest
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = json.NewDecoder(r.Body).Decode(&buildRequest); err != nil {
|
|
||||||
return fmt.Errorf("HandleBuild: Failed to decode body")
|
|
||||||
}
|
|
||||||
|
|
||||||
if builder.Build != nil {
|
|
||||||
slog.Error("HandleBuild: Build started but Build was non-nil")
|
|
||||||
os.Exit(ExitError)
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"--store", path.Join(argBuildPath, "store"),
|
|
||||||
"--cache-max-size", "0",
|
|
||||||
"--output-directory", path.Join(argBuildPath, "export"),
|
|
||||||
"--json",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pipeline := range buildRequest.Pipelines {
|
|
||||||
args = append(args, "--export")
|
|
||||||
args = append(args, pipeline)
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, path.Join(argBuildPath, "manifest.json"))
|
|
||||||
|
|
||||||
envs := os.Environ()
|
|
||||||
envs = append(envs, buildRequest.Environments...)
|
|
||||||
|
|
||||||
builder.Build = &BackgroundProcess{}
|
|
||||||
builder.Build.Process = exec.Command(
|
|
||||||
"/usr/bin/osbuild",
|
|
||||||
args...,
|
|
||||||
)
|
|
||||||
builder.Build.Process.Env = envs
|
|
||||||
|
|
||||||
slog.Info("BackgroundProcess: Starting", "process", builder.Build.Process, "env", envs)
|
|
||||||
|
|
||||||
builder.Build.Stdout = &bytes.Buffer{}
|
|
||||||
builder.Build.Process.Stdout = builder.Build.Stdout
|
|
||||||
|
|
||||||
builder.Build.Stderr, err = builder.Build.Process.StderrPipe()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := builder.Build.Process.Start(); err != nil {
|
|
||||||
return fmt.Errorf("BackgroundProcess: Failed to start process")
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
builder.Build.Error = builder.Build.Process.Wait()
|
|
||||||
builder.Build.Done = true
|
|
||||||
|
|
||||||
slog.Info("BackgroundProcess: Exited")
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
scanner := bufio.NewScanner(builder.Build.Stderr)
|
|
||||||
for scanner.Scan() {
|
|
||||||
m := scanner.Text()
|
|
||||||
slog.Info("BackgroundProcess: Stderr", "text", m)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
|
|
||||||
builder.SetState(StateProgress)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (builder *Builder) HandleProgress(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
builder.GuardState(StateProgress)
|
|
||||||
|
|
||||||
if r.Method != "GET" {
|
|
||||||
return fmt.Errorf("Builder.HandleProgress: Unexpected request method")
|
|
||||||
}
|
|
||||||
|
|
||||||
if builder.Build == nil {
|
|
||||||
return fmt.Errorf("HandleProgress: Progress requested but Build was nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if builder.Build.Done {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
if builder.Build.Error != nil {
|
|
||||||
return fmt.Errorf("Builder.HandleBuild: Buildprocess exited with error: %s", builder.Build.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := w.Write(builder.Build.Stdout.Bytes()); err != nil {
|
|
||||||
return fmt.Errorf("Builder.HandleBuild: Failed to write response")
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.SetState(StateExport)
|
|
||||||
} else {
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("Builder.HandleBuild: Done")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (builder *Builder) HandleExport(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
builder.GuardState(StateExport)
|
|
||||||
|
|
||||||
if r.Method != "GET" {
|
|
||||||
return fmt.Errorf("Builder.HandleExport: unexpected request method")
|
|
||||||
}
|
|
||||||
|
|
||||||
exportPath := r.URL.Query().Get("path")
|
|
||||||
|
|
||||||
if exportPath == "" {
|
|
||||||
return fmt.Errorf("Builder.HandleExport: Missing export")
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX check subdir
|
|
||||||
srcPath := path.Join(argBuildPath, "export", exportPath)
|
|
||||||
|
|
||||||
src, err := os.Open(
|
|
||||||
srcPath,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Builder.HandleExport: Failed to open source: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(w, src)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Builder.HandleExport: Failed to write response: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("Builder.HandleExport: Done")
|
|
||||||
|
|
||||||
builder.SetState(StateDone)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (builder *Builder) Serve() error {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/claim", builder.RegisterHandler(builder.HandleClaim))
|
|
||||||
|
|
||||||
mux.HandleFunc("/provision", builder.RegisterHandler(builder.HandleProvision))
|
|
||||||
mux.HandleFunc("/populate", builder.RegisterHandler(builder.HandlePopulate))
|
|
||||||
|
|
||||||
mux.HandleFunc("/build", builder.RegisterHandler(builder.HandleBuild))
|
|
||||||
mux.HandleFunc("/progress", builder.RegisterHandler(builder.HandleProgress))
|
|
||||||
|
|
||||||
mux.HandleFunc("/export", builder.RegisterHandler(builder.HandleExport))
|
|
||||||
|
|
||||||
/* #nosec G112 */
|
|
||||||
builder.net = &http.Server{
|
|
||||||
Addr: fmt.Sprintf("%s:%d", builder.Host, builder.Port),
|
|
||||||
Handler: mux,
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.net.ListenAndServe()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
slog.With(
|
|
||||||
slog.Bool("argJSON", argJSON),
|
|
||||||
slog.String("argBuilderHost", argBuilderHost),
|
|
||||||
slog.Int("argBuilderPort", argBuilderPort),
|
|
||||||
slog.Int("argTimeoutClaim", argTimeoutClaim),
|
|
||||||
slog.Int("argTimeoutProvision", argTimeoutProvision),
|
|
||||||
slog.Int("argTimeoutBuild", argTimeoutBuild),
|
|
||||||
slog.Int("argTimeoutExport", argTimeoutExport),
|
|
||||||
).Info("main: Starting up")
|
|
||||||
|
|
||||||
builder := Builder{
|
|
||||||
State: StateClaim,
|
|
||||||
StateChannel: make(chan State, 1),
|
|
||||||
Host: argBuilderHost,
|
|
||||||
Port: argBuilderPort,
|
|
||||||
}
|
|
||||||
|
|
||||||
errs := make(chan error, 1)
|
|
||||||
|
|
||||||
go func(errs chan<- error) {
|
|
||||||
if err := builder.Serve(); err != nil {
|
|
||||||
errs <- err
|
|
||||||
}
|
|
||||||
}(errs)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case state := <-builder.StateChannel:
|
|
||||||
if state == StateDone {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
if err := builder.net.Shutdown(ctx); err != nil {
|
|
||||||
slog.Error("main: server shutdown failed", "err", err)
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
slog.Info("main: Shutting down successfully")
|
|
||||||
os.Exit(ExitOk)
|
|
||||||
}
|
|
||||||
case err := <-errs:
|
|
||||||
slog.Error("ErrorChannel", "err", err)
|
|
||||||
os.Exit(ExitError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,483 +0,0 @@
|
||||||
// # `jobsite-manager`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ExitOk int = iota
|
|
||||||
ExitError
|
|
||||||
ExitTimeout
|
|
||||||
ExitSignal
|
|
||||||
)
|
|
||||||
|
|
||||||
type ArgumentList []string
|
|
||||||
|
|
||||||
func (AL *ArgumentList) String() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (AL *ArgumentList) Set(value string) error {
|
|
||||||
*AL = append(*AL, value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
argJSON bool
|
|
||||||
|
|
||||||
argJobsiteHost string
|
|
||||||
argJobsitePort int
|
|
||||||
argBuilderHost string
|
|
||||||
argBuilderPort int
|
|
||||||
|
|
||||||
argTimeoutClaim int
|
|
||||||
argTimeoutProvision int
|
|
||||||
argTimeoutPopulate int
|
|
||||||
argTimeoutBuild int
|
|
||||||
argTimeoutProgress int
|
|
||||||
argTimeoutExport int
|
|
||||||
|
|
||||||
argPipelines ArgumentList
|
|
||||||
argEnvironments ArgumentList
|
|
||||||
argExports ArgumentList
|
|
||||||
argOutputPath string
|
|
||||||
argStore string
|
|
||||||
)
|
|
||||||
|
|
||||||
type BuildRequest struct {
|
|
||||||
Pipelines []string `json:"pipelines"`
|
|
||||||
Environments []string `json:"environments"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Step func(chan<- struct{}, chan<- error)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.BoolVar(&argJSON, "json", false, "Enable JSON output")
|
|
||||||
|
|
||||||
flag.StringVar(&argJobsiteHost, "manager-host", "localhost", "Hostname or IP where this program will listen on.")
|
|
||||||
flag.IntVar(&argJobsitePort, "manager-port", 3333, "Port this program will listen on.")
|
|
||||||
|
|
||||||
flag.StringVar(&argBuilderHost, "builder-host", "localhost", "Hostname or IP of a jobsite-builder that this program will connect to.")
|
|
||||||
flag.IntVar(&argBuilderPort, "builder-port", 3333, "Port of a jobsite-builder that this program will connect to.")
|
|
||||||
|
|
||||||
flag.IntVar(&argTimeoutClaim, "timeout-claim", 600, "Timeout before the claim phase needs to be completed in seconds.")
|
|
||||||
flag.IntVar(&argTimeoutProvision, "timeout-provision", 30, "Timeout before the provision phase needs to be completed in seconds.")
|
|
||||||
flag.IntVar(&argTimeoutPopulate, "timeout-populate", 300, "Timeout before the populate phase needs to be completed in seconds.")
|
|
||||||
flag.IntVar(&argTimeoutBuild, "timeout-build", 30, "Timeout before the build phase needs to be completed in seconds.")
|
|
||||||
flag.IntVar(&argTimeoutProgress, "timeout-progress", 3600, "Timeout before the progress phase needs to be completed in seconds.")
|
|
||||||
flag.IntVar(&argTimeoutExport, "timeout-export", 1800, "Timeout before the export phase needs to be completed in seconds.")
|
|
||||||
|
|
||||||
flag.Var(&argPipelines, "export", "Pipelines to export. Can be passed multiple times.")
|
|
||||||
flag.Var(&argExports, "export-file", "Files to export. Can be passed multiple times.")
|
|
||||||
|
|
||||||
flag.Var(&argEnvironments, "environment", "Environments to add. Can be passed multiple times.")
|
|
||||||
flag.StringVar(&argOutputPath, "output", "/dev/null", "Output directory to write to.")
|
|
||||||
flag.StringVar(&argStore, "store", "", "Store to send to builder.")
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
opts := &slog.HandlerOptions{Level: slog.LevelInfo}
|
|
||||||
if argJSON {
|
|
||||||
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, opts)))
|
|
||||||
} else {
|
|
||||||
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, opts)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
slog.Info("main: Starting up")
|
|
||||||
|
|
||||||
sigs := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
|
|
||||||
done := make(chan struct{}, 1)
|
|
||||||
errs := make(chan error, 1)
|
|
||||||
|
|
||||||
go Dance(done, errs)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case sig := <-sigs:
|
|
||||||
slog.Info("main: Exiting on signal", "signal", sig)
|
|
||||||
os.Exit(ExitSignal)
|
|
||||||
case err := <-errs:
|
|
||||||
slog.Info("main: Exiting on error", "error", err)
|
|
||||||
os.Exit(ExitError)
|
|
||||||
case <-done:
|
|
||||||
slog.Info("main: Shutting down succesfully")
|
|
||||||
os.Exit(ExitOk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Dance(done chan<- struct{}, errs chan<- error) {
|
|
||||||
manifest, err := io.ReadAll(os.Stdin)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := StepClaim(); err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := StepProvision(manifest); err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := StepPopulate(); err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := StepBuild(); err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := StepProgress(); err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := StepExport(); err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
close(done)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Request(method string, path string, body io.Reader, bodySeeker io.ReadSeeker) (*http.Response, error) {
|
|
||||||
ctx := context.Background()
|
|
||||||
cli := &http.Client{}
|
|
||||||
url := fmt.Sprintf("http://%s:%d/%s", argBuilderHost, argBuilderPort, path)
|
|
||||||
|
|
||||||
var req *http.Request
|
|
||||||
var err error
|
|
||||||
if bodySeeker != nil {
|
|
||||||
req, err = http.NewRequestWithContext(ctx, method, url, bodySeeker)
|
|
||||||
} else {
|
|
||||||
req, err = http.NewRequestWithContext(ctx, method, url, body)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't accept any compression on the return value; it intermittently leads to unexpected EOFs during the larger
|
|
||||||
// download when the exports are requested.
|
|
||||||
req.Header.Set("Accept-Encoding", "identity")
|
|
||||||
|
|
||||||
slog.DebugContext(ctx, "Request: Making a request", "method", method, "url", url)
|
|
||||||
|
|
||||||
for {
|
|
||||||
res, err := cli.Do(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, syscall.ECONNABORTED) || errors.Is(err, syscall.ECONNRESET) || errors.Is(err, syscall.ECONNREFUSED) {
|
|
||||||
if bodySeeker != nil {
|
|
||||||
_, err = bodySeeker.Seek(0, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Wait(timeout int, fn Step) error {
|
|
||||||
done := make(chan struct{}, 1)
|
|
||||||
errs := make(chan error, 1)
|
|
||||||
|
|
||||||
go fn(done, errs)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(time.Duration(timeout) * time.Second):
|
|
||||||
return fmt.Errorf("timeout")
|
|
||||||
case <-done:
|
|
||||||
return nil
|
|
||||||
case err := <-errs:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func StepClaim() error {
|
|
||||||
return Wait(argTimeoutClaim, func(done chan<- struct{}, errs chan<- error) {
|
|
||||||
res, err := Request("POST", "claim", nil, nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
|
||||||
errs <- fmt.Errorf("StepClaim: Got an unexpected response %d while expecting %d. Exiting", res.StatusCode, http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("StepClaim: Done")
|
|
||||||
|
|
||||||
close(done)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func StepProvision(manifest []byte) error {
|
|
||||||
return Wait(argTimeoutProvision, func(done chan<- struct{}, errs chan<- error) {
|
|
||||||
res, err := Request("PUT", "provision", bytes.NewBuffer(manifest), nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
if res.StatusCode != http.StatusCreated {
|
|
||||||
errs <- fmt.Errorf("StepProvision: Got an unexpected response %d while expecting %d. Exiting", res.StatusCode, http.StatusCreated)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("StepProvision: Done")
|
|
||||||
|
|
||||||
close(done)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func StepPopulate() error {
|
|
||||||
return Wait(argTimeoutPopulate, func(done chan<- struct{}, errs chan<- error) {
|
|
||||||
file, err := os.CreateTemp(filepath.Dir(argStore), "store.*.tar")
|
|
||||||
if err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(file.Name())
|
|
||||||
|
|
||||||
tw := tar.NewWriter(file)
|
|
||||||
defer tw.Close()
|
|
||||||
err = filepath.Walk(argStore, func(filePath string, fi os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip the store direction itself, just package the contents
|
|
||||||
if filePath == argStore {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fi.Mode().IsRegular() && !fi.Mode().IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
header, err := tar.FileInfoHeader(fi, fi.Name())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileInfo only contains the basename, see https://pkg.go.dev/archive/tar#FileInfoHeader.
|
|
||||||
header.Name = strings.TrimPrefix(strings.Replace(filePath, argStore, "", -1), string(filepath.Separator))
|
|
||||||
if err := tw.WriteHeader(header); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.Mode().IsRegular() {
|
|
||||||
f, err := os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(tw, f); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tw.Close()
|
|
||||||
if err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = file.Seek(0, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := Request("POST", "populate", nil, file)
|
|
||||||
if err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
|
||||||
errs <- fmt.Errorf("StepPopulate: Got an unexpected response %d while expecting %d. Exiting", res.StatusCode, http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("StepPopulate: Done")
|
|
||||||
|
|
||||||
close(done)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func StepBuild() error {
|
|
||||||
return Wait(argTimeoutBuild, func(done chan<- struct{}, errs chan<- error) {
|
|
||||||
arg := BuildRequest{
|
|
||||||
Pipelines: argPipelines,
|
|
||||||
Environments: argEnvironments,
|
|
||||||
}
|
|
||||||
|
|
||||||
dat, err := json.Marshal(arg)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("StepBuild: Failed to marshal data", "err", err)
|
|
||||||
os.Exit(ExitError)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := Request("POST", "build", bytes.NewBuffer(dat), nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
if res.StatusCode != http.StatusCreated {
|
|
||||||
errs <- fmt.Errorf("StepBuild: Got an unexpected response %d while expecting %d. Exiting", res.StatusCode, http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("StepBuild: Done")
|
|
||||||
|
|
||||||
close(done)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func StepProgress() error {
|
|
||||||
return Wait(argTimeoutProgress, func(done chan<- struct{}, errs chan<- error) {
|
|
||||||
for {
|
|
||||||
res, err := Request("GET", "progress", nil, nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
if res.StatusCode == http.StatusAccepted {
|
|
||||||
slog.Info("StepProgress: Build is pending. Retry")
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
|
||||||
errs <- fmt.Errorf("StepProgress: Got an unexpected response %d while expecting %d. Exiting", res.StatusCode, http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(os.Stdout, res.Body)
|
|
||||||
if err != nil {
|
|
||||||
errs <- fmt.Errorf("StepProgress: Unable to write response body to stdout: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("StepProgress: Done")
|
|
||||||
|
|
||||||
close(done)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func StepExport() error {
|
|
||||||
return Wait(argTimeoutExport, func(done chan<- struct{}, errs chan<- error) {
|
|
||||||
for _, export := range argExports {
|
|
||||||
res, err := Request("GET", fmt.Sprintf("export?path=%s", url.PathEscape(export)), nil, nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errs <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
|
||||||
errs <- fmt.Errorf("StepExport: Got an unexpected response %d while expecting %d. Exiting", res.StatusCode, http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dstPath := path.Join(argOutputPath, export)
|
|
||||||
dstDir := filepath.Dir(dstPath)
|
|
||||||
|
|
||||||
if _, err := os.Stat(dstDir); os.IsNotExist(err) {
|
|
||||||
slog.Info("StepExport: Destination directory does not exist. Creating", "dir", dstDir)
|
|
||||||
if err := os.MkdirAll(dstDir, 0700); err != nil {
|
|
||||||
errs <- fmt.Errorf("StepExport: Failed to create destination directory: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dst, err := os.OpenFile(
|
|
||||||
path.Join(argOutputPath, export),
|
|
||||||
os.O_WRONLY|os.O_CREATE|os.O_EXCL,
|
|
||||||
0400,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errs <- fmt.Errorf("StepExport: Failed to open destination response: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(dst, res.Body)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errs <- fmt.Errorf("StepExport: Failed to copy response: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("StepExport: Done")
|
|
||||||
|
|
||||||
close(done)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -106,8 +106,6 @@ export LDFLAGS="${LDFLAGS} -X 'github.com/osbuild/osbuild-composer/internal/comm
|
||||||
%gobuild ${GOTAGS:+-tags=$GOTAGS} -o _bin/osbuild-composer %{goipath}/cmd/osbuild-composer
|
%gobuild ${GOTAGS:+-tags=$GOTAGS} -o _bin/osbuild-composer %{goipath}/cmd/osbuild-composer
|
||||||
%gobuild ${GOTAGS:+-tags=$GOTAGS} -o _bin/osbuild-worker %{goipath}/cmd/osbuild-worker
|
%gobuild ${GOTAGS:+-tags=$GOTAGS} -o _bin/osbuild-worker %{goipath}/cmd/osbuild-worker
|
||||||
%gobuild ${GOTAGS:+-tags=$GOTAGS} -o _bin/osbuild-worker-executor %{goipath}/cmd/osbuild-worker-executor
|
%gobuild ${GOTAGS:+-tags=$GOTAGS} -o _bin/osbuild-worker-executor %{goipath}/cmd/osbuild-worker-executor
|
||||||
%gobuild ${GOTAGS:+-tags=$GOTAGS} -o _bin/osbuild-jobsite-manager %{goipath}/cmd/osbuild-jobsite-manager
|
|
||||||
%gobuild ${GOTAGS:+-tags=$GOTAGS} -o _bin/osbuild-jobsite-builder %{goipath}/cmd/osbuild-jobsite-builder
|
|
||||||
|
|
||||||
make man
|
make man
|
||||||
|
|
||||||
|
|
@ -140,8 +138,6 @@ install -m 0755 -vd %{buildroot}%
|
||||||
install -m 0755 -vp _bin/osbuild-composer %{buildroot}%{_libexecdir}/osbuild-composer/
|
install -m 0755 -vp _bin/osbuild-composer %{buildroot}%{_libexecdir}/osbuild-composer/
|
||||||
install -m 0755 -vp _bin/osbuild-worker %{buildroot}%{_libexecdir}/osbuild-composer/
|
install -m 0755 -vp _bin/osbuild-worker %{buildroot}%{_libexecdir}/osbuild-composer/
|
||||||
install -m 0755 -vp _bin/osbuild-worker-executor %{buildroot}%{_libexecdir}/osbuild-composer/
|
install -m 0755 -vp _bin/osbuild-worker-executor %{buildroot}%{_libexecdir}/osbuild-composer/
|
||||||
install -m 0755 -vp _bin/osbuild-jobsite-manager %{buildroot}%{_libexecdir}/osbuild-composer/
|
|
||||||
install -m 0755 -vp _bin/osbuild-jobsite-builder %{buildroot}%{_libexecdir}/osbuild-composer/
|
|
||||||
|
|
||||||
# Only include repositories for the distribution and release
|
# Only include repositories for the distribution and release
|
||||||
install -m 0755 -vd %{buildroot}%{_datadir}/osbuild-composer/repositories
|
install -m 0755 -vd %{buildroot}%{_datadir}/osbuild-composer/repositories
|
||||||
|
|
@ -347,8 +343,6 @@ The worker for osbuild-composer
|
||||||
%files worker
|
%files worker
|
||||||
%{_libexecdir}/osbuild-composer/osbuild-worker
|
%{_libexecdir}/osbuild-composer/osbuild-worker
|
||||||
%{_libexecdir}/osbuild-composer/osbuild-worker-executor
|
%{_libexecdir}/osbuild-composer/osbuild-worker-executor
|
||||||
%{_libexecdir}/osbuild-composer/osbuild-jobsite-manager
|
|
||||||
%{_libexecdir}/osbuild-composer/osbuild-jobsite-builder
|
|
||||||
%{_unitdir}/osbuild-worker@.service
|
%{_unitdir}/osbuild-worker@.service
|
||||||
%{_unitdir}/osbuild-remote-worker@.service
|
%{_unitdir}/osbuild-remote-worker@.service
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,5 +32,5 @@ sinks:
|
||||||
EOF
|
EOF
|
||||||
sudo systemctl enable --now vector
|
sudo systemctl enable --now vector
|
||||||
|
|
||||||
echo "Starting osbuild-jobsite-builder."
|
echo "Starting worker executor"
|
||||||
/usr/libexec/osbuild-composer/osbuild-worker-executor -host 0.0.0.0
|
/usr/libexec/osbuild-composer/osbuild-worker-executor -host 0.0.0.0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue