From 6fe602514c4956b36df0e1b6f6529f471f528dc4 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 14 Jun 2022 10:09:38 -0400 Subject: [PATCH] Make `/usr/bin/coreos-assembler` a Go program, implement `clean` in Go - Converts the entrypoint into Go code - Add an internal library that exposes/wraps `cmdlib.sh` because we have a lot of stuff in there that can't be ported to Go yet. - Add an internal library for running inline (named) bash scripts - Port `clean` to Go This is a pattern I think we'll use to aid the transition; rather than trying to rewrite things wholesale in Go, we'll continue to exec some shell scripts. Gradually perhaps, we may invert some things and change both `cmdlib.sh` and `cmdlib.py` to exec the cosa Go process in some cases too. Closes: https://github.com/coreos/coreos-assembler/issues/2821 (cherry picked from commit aa944b8f6c70cfb2f6aaa2ff5250b170b1b59ab7) --- .gitignore | 1 + Makefile | 15 ++- cmd/clean.go | 55 ++++++++ cmd/coreos-assembler.go | 177 ++++++++++++++++++++++++ go.mod | 3 + internal/pkg/bashexec/bashexec.go | 101 ++++++++++++++ internal/pkg/bashexec/bashexec_test.go | 14 ++ internal/pkg/cosash/cosash.go | 180 +++++++++++++++++++++++++ src/cmd-clean | 68 ---------- src/coreos-assembler | 91 ------------- 10 files changed, 543 insertions(+), 162 deletions(-) create mode 100644 cmd/clean.go create mode 100644 cmd/coreos-assembler.go create mode 100644 go.mod create mode 100644 internal/pkg/bashexec/bashexec.go create mode 100644 internal/pkg/bashexec/bashexec_test.go create mode 100644 internal/pkg/cosash/cosash.go delete mode 100755 src/cmd-clean delete mode 100755 src/coreos-assembler diff --git a/.gitignore b/.gitignore index ab18382565..494336dfe3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ maipo/ .coverage tools/bin .idea +bin/ diff --git a/Makefile b/Makefile index f0cedaba34..4ae949d92d 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ PYIGNORE ?= E128,E241,E402,E501,E722,W503,W504 MANTLE_BINARIES := ore kola plume -all: tools mantle gangplank +all: bin/coreos-assembler tools mantle gangplank src:=$(shell find src -maxdepth 1 -type f -executable -print) pysources=$(shell find src -type f -name '*.py') $(shell for x in $(src); do if head -1 $$x | grep -q python; then echo $$x; fi; done) @@ -30,12 +30,16 @@ else ifeq ($(GOARCH),aarch64) GOARCH="arm64" endif +bin/coreos-assembler: + cd cmd && go build -mod vendor -o ../$@ +.PHONY: bin/coreos-assembler + .%.shellchecked: % ./tests/check_one.sh $< $@ shellcheck: ${src_checked} ${tests_checked} ${cwd_checked} -check: shellcheck flake8 pycheck schema-check mantle-check gangplank-check +check: shellcheck flake8 pycheck schema-check mantle-check gangplank-check cosa-go-check echo OK pycheck: @@ -53,6 +57,11 @@ unittest: COSA_TEST_META_PATH=`pwd`/fixtures \ PYTHONPATH=`pwd`/src python3 -m pytest tests/ +cosa-go-check: + (cd cmd && go test -mod=vendor) + go test -mod=vendor github.com/coreos/coreos-assembler/internal/pkg/bashexec + go test -mod=vendor github.com/coreos/coreos-assembler/internal/pkg/cosash + clean: rm -f ${src_checked} ${tests_checked} ${cwd_checked} rm -rf tools/bin @@ -109,7 +118,7 @@ install: install -d $(DESTDIR)$(PREFIX)/lib/coreos-assembler/cosalib install -D -t $(DESTDIR)$(PREFIX)/lib/coreos-assembler/cosalib $$(find src/cosalib/ -maxdepth 1 -type f) install -d $(DESTDIR)$(PREFIX)/bin - ln -sf ../lib/coreos-assembler/coreos-assembler $(DESTDIR)$(PREFIX)/bin/ + install bin/coreos-assembler $(DESTDIR)$(PREFIX)/bin/ ln -sf ../lib/coreos-assembler/cp-reflink $(DESTDIR)$(PREFIX)/bin/ ln -sf coreos-assembler $(DESTDIR)$(PREFIX)/bin/cosa install -d $(DESTDIR)$(PREFIX)/lib/coreos-assembler/tests/kola diff --git a/cmd/clean.go b/cmd/clean.go new file mode 100644 index 0000000000..085d1a7307 --- /dev/null +++ b/cmd/clean.go @@ -0,0 +1,55 @@ +// See usage below +package main + +import ( + "fmt" + + "github.com/coreos/coreos-assembler/internal/pkg/bashexec" + "github.com/coreos/coreos-assembler/internal/pkg/cosash" +) + +func runClean(argv []string) error { + const cleanUsage = `Usage: coreos-assembler clean --help +coreos-assembler clean [--all] + +Delete all build artifacts. Use --all to also clean the cache/ directory. +` + + all := false + for _, arg := range argv { + switch arg { + case "h": + case "--help": + fmt.Print(cleanUsage) + return nil + case "-a": + case "--all": + all = true + default: + return fmt.Errorf("unrecognized option: %s", arg) + } + } + + sh, err := cosash.NewCosaSh() + if err != nil { + return err + } + if _, err := sh.PrepareBuild(); err != nil { + return err + } + + if all { + priv, err := sh.HasPrivileges() + if err != nil { + return err + } + cmd := "rm -rf cache/*" + if priv { + cmd = fmt.Sprintf("sudo %s", cmd) + } + bashexec.Run("cleanup cache", cmd) + } else { + fmt.Println("Note: retaining cache/") + } + return bashexec.Run("cleanup", "rm -rf builds/* tmp/*") +} diff --git a/cmd/coreos-assembler.go b/cmd/coreos-assembler.go new file mode 100644 index 0000000000..62004dccb3 --- /dev/null +++ b/cmd/coreos-assembler.go @@ -0,0 +1,177 @@ +// This is the primary entrypoint for /usr/bin/coreos-assembler. +package main + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "sort" + "strings" + "syscall" +) + +// commands we'd expect to use in the local dev path +var buildCommands = []string{"init", "fetch", "build", "run", "prune", "clean", "list"} +var advancedBuildCommands = []string{"buildfetch", "buildupload", "oc-adm-release", "push-container", "upload-oscontainer"} +var buildextendCommands = []string{"aliyun", "aws", "azure", "digitalocean", "exoscale", "gcp", "ibmcloud", "kubevirt", "live", "metal", "metal4k", "nutanix", "openstack", "qemu", "secex", "virtualbox", "vmware", "vultr"} +var utilityCommands = []string{"aws-replicate", "compress", "generate-hashlist", "koji-upload", "kola", "remote-build-container", "remote-prune", "sign", "tag"} +var otherCommands = []string{"shell", "meta"} + +func init() { + // Note buildCommands is intentionally listed in frequency order + sort.Strings(advancedBuildCommands) + sort.Strings(buildextendCommands) + sort.Strings(utilityCommands) + sort.Strings(otherCommands) +} + +func wrapCommandErr(err error) error { + if err == nil { + return nil + } + if exiterr, ok := err.(*exec.ExitError); ok { + return fmt.Errorf("%w\n%s", err, exiterr.Stderr) + } + return err +} + +func printCommands(title string, cmds []string) { + fmt.Printf("%s:\n", title) + for _, cmd := range cmds { + fmt.Printf(" %s\n", cmd) + } +} + +func printUsage() { + fmt.Println("Usage: coreos-assembler CMD ...") + printCommands("Build commands", buildCommands) + printCommands("Advanced build commands", advancedBuildCommands) + printCommands("Platform builds", buildextendCommands) + printCommands("Utility commands", utilityCommands) + printCommands("Other commands", otherCommands) +} + +func run(argv []string) error { + if err := initializeGlobalState(argv); err != nil { + return fmt.Errorf("failed to initialize global state: %w", err) + } + + var cmd string + if len(argv) > 0 { + cmd = argv[0] + argv = argv[1:] + } + + if cmd == "" { + printUsage() + os.Exit(1) + } + + // Manual argument parsing here for now; once we get to "phase 1" + // of the Go conversion we can vendor cobra (and other libraries) + // at the toplevel. + switch cmd { + case "clean": + return runClean(argv) + } + + target := fmt.Sprintf("/usr/lib/coreos-assembler/cmd-%s", cmd) + _, err := os.Stat(target) + if err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("unknown command: %s", cmd) + } + return fmt.Errorf("failed to stat %s: %w", target, err) + } + + c := exec.Command(target, argv...) + c.Stdin = os.Stdin + c.Stdout = os.Stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + return fmt.Errorf("failed to execute cmd-%s: %w", cmd, err) + } + return nil +} + +func initializeGlobalState(argv []string) error { + // Set PYTHONUNBUFFERED=1 so that we get unbuffered output. We should + // be able to do this on the shebang lines but env doesn't support args + // right now. In Fedora we should be able to use the `env -S` option. + os.Setenv("PYTHONUNBUFFERED", "1") + + // docker/podman don't run through PAM, but we want this set for the privileged + // (non-virtualized) path + user, ok := os.LookupEnv("USER") + if !ok { + b, err := exec.Command("id", "-nu").Output() + if err == nil { + user = strings.TrimSpace(string(b)) + } else { + user = "cosa" + } + os.Setenv("USER", user) + } + + // https://github.com/containers/libpod/issues/1448 + // if /sys/fs/selinux is mounted, various tools will think they're on a SELinux enabled + // host system, and we don't want that. Work around this by overmounting it. + // So far we only see /sys/fs/selinux mounted in a privileged container, so we know we + // have privileges to create a new mount namespace and overmount it with an empty directory. + const selinuxfs = "/sys/fs/selinux" + if _, err := os.Stat(selinuxfs + "/status"); err == nil { + const unsharedKey = "coreos_assembler_unshared" + if _, ok := os.LookupEnv(unsharedKey); ok { + err := exec.Command("sudo", "mount", "--bind", "/usr/share/empty", "/sys/fs/selinux").Run() + if err != nil { + return fmt.Errorf("failed to unmount %s: %w", selinuxfs, wrapCommandErr(err)) + } + } else { + fmt.Fprintf(os.Stderr, "warning: %s appears to be mounted but should not be; enabling workaround\n", selinuxfs) + selfpath, err := os.Readlink("/proc/self/exe") + if err != nil { + return err + } + baseArgv := []string{"sudo", "-E", "--", "env", fmt.Sprintf("%s=1", unsharedKey), "unshare", "-m", "--", "runuser", "-u", user, "--", selfpath} + err = syscall.Exec("/usr/bin/sudo", append(baseArgv, argv...), os.Environ()) + return fmt.Errorf("failed to re-exec self to unmount %s: %w", selinuxfs, err) + } + } + + // When trying to connect to libvirt we get "Failed to find user record + // for uid" errors if there is no entry for our UID in /etc/passwd. + // This was taken from 'Support Arbitrary User IDs' section of: + // https://docs.openshift.com/container-platform/3.10/creating_images/guidelines.html + c := exec.Command("whoami") + c.Stdout = ioutil.Discard + c.Stderr = ioutil.Discard + if err := c.Run(); err != nil { + fmt.Fprintln(os.Stderr, "notice: failed to look up uid in /etc/passwd; enabling workaround") + home := fmt.Sprintf("/var/tmp/%s", user) + err := os.MkdirAll(home, 0755) + if err != nil { + return err + } + f, err := os.OpenFile("/etc/passwd", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("opening /etc/passwd: %w", err) + } + defer f.Close() + id := os.Getuid() + buf := fmt.Sprintf("%s:x:%d:0:%s user:%s:/sbin/nologin\n", user, id, user, home) + if _, err = f.WriteString(buf); err != nil { + return err + } + } + + return nil +} + +func main() { + err := run(os.Args[1:]) + if err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000..2e394d206e --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/coreos/coreos-assembler + +go 1.15 diff --git a/internal/pkg/bashexec/bashexec.go b/internal/pkg/bashexec/bashexec.go new file mode 100644 index 0000000000..45de4a9373 --- /dev/null +++ b/internal/pkg/bashexec/bashexec.go @@ -0,0 +1,101 @@ +// Package bashexec provides helpers to execute bash code. +// What this primarily offers over directly writing e.g. `exec.Command("bash")` +// is: +// +// - By default, all fragments are executed in "bash strict mode": http://redsymbol.net/articles/unofficial-bash-strict-mode/ +// - The code encourages adding a "name" for in-memory scripts, similar to e.g. +// Ansible tasks as well as many CI systems like Github actions +// - The code to execute is piped to stdin instead of passed via `-c` which +// avoids argument length limits and makes the output of e.g. `ps` readable. +// - Scripts are assumed synchronous, and stdin/stdout/stderr are passed directly +// instead of piped. +// - We use prctl(PR_SET_PDEATHSIG) (assuming Linux) to lifecycle bind the script to the caller +// +package bashexec + +import ( + "fmt" + "io" + "os" + "os/exec" + "strings" + "syscall" +) + +// StrictMode enables http://redsymbol.net/articles/unofficial-bash-strict-mode/ +const StrictMode = "set -euo pipefail" + +// BashRunner is a wrapper for executing in-memory bash scripts +type BashRunner struct { + name string + cmd *exec.Cmd +} + +// NewBashRunner creates a bash executor from in-memory shell script. +func NewBashRunner(name, src string, args ...string) (*BashRunner, error) { + // This will be proxied to fd 3 + f, err := os.CreateTemp("", name) + if err != nil { + return nil, err + } + if _, err := io.Copy(f, strings.NewReader(src)); err != nil { + return nil, err + } + if err := os.Remove(f.Name()); err != nil { + return nil, err + } + + bashCmd := fmt.Sprintf("%s\n. /proc/self/fd/3\n", StrictMode) + fullargs := append([]string{"-c", bashCmd, name}, args...) + cmd := exec.Command("/bin/bash", fullargs...) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGTERM, + } + cmd.Stdin = os.Stdin + cmd.ExtraFiles = append(cmd.ExtraFiles, f) + + return &BashRunner{ + name: name, + cmd: cmd, + }, nil +} + +// Exec synchronously spawns the child process, passing stdin/stdout/stderr directly. +func (r *BashRunner) Exec() error { + r.cmd.Stdin = os.Stdin + r.cmd.Stdout = os.Stdout + r.cmd.Stderr = os.Stderr + err := r.cmd.Run() + if err != nil { + return fmt.Errorf("failed to execute internal script %s: %w", r.name, err) + } + return nil +} + +// Run spawns the script, gathering stdout/stderr into a buffer that is displayed only on error. +func (r *BashRunner) Run() error { + buf, err := r.cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to execute internal script %s: %w\n%s", r.name, err, buf) + } + return nil +} + +// Run spawns a named script (without any arguments), +// gathering stdout/stderr into a buffer that is displayed only on error. +func Run(name, cmd string) error { + sh, err := NewBashRunner(name, cmd) + if err != nil { + return err + } + return sh.Run() +} + +// RunA spawns an anonymous script, and is otherwise the same as `Run`. +func RunA(cmd string) error { + sh, err := NewBashRunner("", cmd) + if err != nil { + return err + } + return sh.Run() +} diff --git a/internal/pkg/bashexec/bashexec_test.go b/internal/pkg/bashexec/bashexec_test.go new file mode 100644 index 0000000000..27293cebad --- /dev/null +++ b/internal/pkg/bashexec/bashexec_test.go @@ -0,0 +1,14 @@ +package bashexec + +import "testing" + +func TestBashExec(t *testing.T) { + err := Run("true", "true") + if err != nil { + panic(err) + } + err = Run("task that should fail", "false") + if err == nil { + panic("expected err") + } +} diff --git a/internal/pkg/cosash/cosash.go b/internal/pkg/cosash/cosash.go new file mode 100644 index 0000000000..6e64bb75d8 --- /dev/null +++ b/internal/pkg/cosash/cosash.go @@ -0,0 +1,180 @@ +// Package cosash implements a "co-processing" proxy that is primarily +// designed to expose a Go API that is currently implemented by `src/cmdlib.sh`. +// A lot of the code in that file is stateful - e.g. APIs set environment variables +// and allocate temporary directories. So it wouldn't work very well to fork +// a new shell process each time. +// +// The "co-processing" here is a way to describe that there's intended to be +// a one-to-one relationship of the child bash process and the current one, +// although this is not strictly required. The Go APIs here call dynamically +// into the bash process by writing to its stdin, and can receive serialized +// data back over a pipe on file descriptor 3. +package cosash + +import ( + "bufio" + "fmt" + "io" + "os" + "os/exec" + "strconv" + "strings" + "syscall" + + "github.com/coreos/coreos-assembler/internal/pkg/bashexec" +) + +// CosaSh is a companion shell process which accepts commands +// piped over stdin. +type CosaSh struct { + cmd *exec.Cmd + input io.WriteCloser + preparedBuild bool + ackserial uint64 + replychan <-chan (string) + errchan <-chan (error) +} + +func parseAck(r *bufio.Reader, expected uint64) (string, error) { + linebytes, _, err := r.ReadLine() + if err != nil { + return "", err + } + line := string(linebytes) + parts := strings.SplitN(line, " ", 2) + if len(parts) != 2 { + return "", fmt.Errorf("invalid reply from cosash: %s", line) + } + serial, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return "", fmt.Errorf("invalid reply from cosash: %s", line) + } + if serial != expected { + return "", fmt.Errorf("unexpected ack serial from cosash; expected=%d reply=%d", expected, serial) + } + return parts[1], nil +} + +// NewCosaSh creates a new companion shell process +func NewCosaSh() (*CosaSh, error) { + cmd := exec.Command("/bin/bash") + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGTERM, + } + // This is the channel where we send our commands + input, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + // stdout and stderr are the same as ours; we are effectively + // "co-processing", so we want to get output/errors as they're + // printed. + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + cmdin, cmdout, err := os.Pipe() + if err != nil { + return nil, err + } + cmd.ExtraFiles = append(cmd.ExtraFiles, cmdout) + + // Start the process + if err := cmd.Start(); err != nil { + return nil, err + } + + replychan := make(chan string) + errchan := make(chan error) + + r := &CosaSh{ + input: input, + cmd: cmd, + replychan: replychan, + errchan: errchan, + preparedBuild: false, + } + + // Send a message when the process exits + go func() { + errchan <- cmd.Wait() + }() + // Parse the ack serials into a channel + go func() { + bufr := bufio.NewReader(cmdin) + for { + reply, err := parseAck(bufr, r.ackserial) + if err != nil { + // Don't propagate EOF, since we want the process exit status instead. + if err == io.EOF { + break + } + errchan <- err + break + } + r.ackserial += 1 + replychan <- reply + } + }() + + // Initialize the internal library + err = r.process(fmt.Sprintf("%s\n. /usr/lib/coreos-assembler/cmdlib.sh\n", bashexec.StrictMode)) + if err != nil { + return nil, fmt.Errorf("failed to init cosash: %w", err) + } + + return r, nil +} + +// write sends content to the shell's stdin, synchronously wait for the reply +func (r *CosaSh) processWithReply(buf string) (string, error) { + // Inject code which writes the serial reply prefix + cmd := fmt.Sprintf("echo -n \"%d \" >&3\n", r.ackserial) + if _, err := io.WriteString(r.input, cmd); err != nil { + return "", err + } + // Tell the shell to execute the code, which should write the reply to fd 3 + // which will complete the command. + if _, err := io.WriteString(r.input, buf); err != nil { + return "", err + } + + select { + case reply := <-r.replychan: + return reply, nil + case err := <-r.errchan: + return "", err + } +} + +func (sh *CosaSh) process(buf string) error { + buf = fmt.Sprintf("%s\necho OK >&3\n", buf) + r, err := sh.processWithReply(buf) + if err != nil { + return err + } + if r != "OK" { + return fmt.Errorf("unexpected reply from cosash; expected OK, found %s", r) + } + return nil +} + +// PrepareBuild prepares for a build, returning the newly allocated build directory +func (sh *CosaSh) PrepareBuild() (string, error) { + return sh.processWithReply(`prepare_build +pwd >&3 +`) +} + +// HasPrivileges checks if we can use sudo +func (sh *CosaSh) HasPrivileges() (bool, error) { + r, err := sh.processWithReply(` +if has_privileges; then + echo true >&3 +else + echo false >&3 +fi`) + if err != nil { + return false, err + } + return strconv.ParseBool(r) +} diff --git a/src/cmd-clean b/src/cmd-clean deleted file mode 100755 index 960d475a27..0000000000 --- a/src/cmd-clean +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -dn=$(dirname "$0") -# shellcheck source=src/cmdlib.sh -. "${dn}"/cmdlib.sh - -print_help() { - cat 1>&2 <<'EOF' -Usage: coreos-assembler clean --help - coreos-assembler clean [--all] - - Delete all build artifacts. Use --all to also clean the cache/ directory. -EOF -} - -rc=0 -all=0 -options=$(getopt --options ah --longoptions all,help -- "$@") || rc=$? -[ $rc -eq 0 ] || { - print_help - exit 1 -} -eval set -- "$options" -while true; do - case "$1" in - -h | --help) - print_help - exit 0 - ;; - -a | --all) - all=1 - ;; - --) - shift - break - ;; - *) - fatal "$0: unrecognized option: $1" - exit 1 - ;; - esac - shift -done - -if [ $# -ne 0 ]; then - print_help - fatal "ERROR: Too many arguments" - exit 1 -fi - -set -x -# This has some useful sanity checks -prepare_build - -# But go back to the toplevel -cd "${workdir:?}" -# We don't clean the cache by default. -if test "${all}" = "1"; then - if has_privileges; then - sudo rm -rf cache/* - else - rm -rf cache/* - fi -else - echo "Note: retaining cache/" -fi -rm -rf builds/* tmp/* diff --git a/src/coreos-assembler b/src/coreos-assembler deleted file mode 100755 index 215be0ffdc..0000000000 --- a/src/coreos-assembler +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -# Usage: coreos-assembler ... -# Currently this just wraps the two binaries we have today -# under a global entrypoint with subcommands. - -# Set PYTHONUNBUFFERED=1 so that we get unbuffered output. We should -# be able to do this on the shebang lines but env doesn't support args -# right now. In Fedora we should be able to use the `env -S` option. -export PYTHONUNBUFFERED=1 - -# docker/podman don't run through PAM, but we want this set for the privileged -# (non-virtualized) path -export USER="${USER:-$(id -nu)}" - -# When trying to connect to libvirt we get "Failed to find user record -# for uid" errors if there is no entry for our UID in /etc/passwd. -# This was taken from 'Support Arbitrary User IDs' section of: -# https://docs.openshift.com/container-platform/3.10/creating_images/guidelines.html -if ! whoami &> /dev/null; then - # We need to make sure we set $HOME in the /etc/passwd file because - # if we don't libvirt will try to use `/` and we will get permission - # issues - export HOME="/var/tmp/${USER_NAME:-default}" && mkdir -p "$HOME" - if [ -w /etc/passwd ]; then - echo "${USER_NAME:-default}:x:$(id -u):0:${USER_NAME:-default} user:${HOME}:/sbin/nologin" >> /etc/passwd - fi -fi - -# Ensure we've unshared our mount namespace so -# the later umount doesn't affect the host potentially -if [ -e /sys/fs/selinux/status ]; then - if [ -z "${coreos_assembler_unshared:-}" ]; then - exec sudo -E -- env coreos_assembler_unshared=1 unshare -m -- runuser -u "${USER}" -- "$0" "$@" - else - # Work around https://github.com/containers/libpod/issues/1448 - # https://github.com/cgwalters/coretoolbox/blob/04e36894cdb912cd4d4c91b26436c57a2d96707d/src/coretoolbox.rs#L616 - sudo mount --bind /usr/share/empty /sys/fs/selinux - fi -fi - -cmd=${1:-} -# commands we'd expect to use in the local dev path -build_commands="init fetch build run prune clean list" -# commands more likely to be used in a prod pipeline only -advanced_build_commands="buildfetch buildupload oc-adm-release push-container upload-oscontainer" -buildextend_commands="aliyun aws azure digitalocean exoscale gcp ibmcloud kubevirt live metal metal4k nutanix openstack qemu virtualbox vmware vultr" -utility_commands="aws-replicate compress generate-hashlist koji-upload kola remote-prune sign tag" -other_commands="shell meta" -if [ -z "${cmd}" ]; then - echo Usage: "coreos-assembler CMD ..." - echo "Build commands:" - for bin in ${build_commands}; do - echo " ${bin}" - done # don't sort these ones, they're roughly in the order they're used - - echo "Advanced build commands:" - for bin in ${advanced_build_commands}; do - echo " ${bin}" - done && for bin in ${buildextend_commands}; do - echo " buildextend-${bin}" - done | sort - - echo "Utility commands:" - for bin in ${utility_commands}; do - echo " ${bin}" - done | sort - - echo "Other commands:" - for bin in ${other_commands}; do - echo " ${bin}" - done | sort - exit 1 -fi -shift - -COSA_META_SCHEMA="${COSA_META_SCHEMA:-/usr/lib/coreos-assembler/v1.json}" -schema_override="${PWD}/src/config/schema.json" -if [ -e "${schema_override}" ]; then - COSA_META_SCHEMA=$(realpath "${schema_override}") -fi -export COSA_META_SCHEMA - - -target=/usr/lib/coreos-assembler/cmd-${cmd} -if test -x "${target}"; then - exec "${target}" "$@" -fi - -echo "Unknown command: ${cmd}" 1>&2 -exit 1