mirror of
https://github.com/coreos/coreos-assembler.git
synced 2026-02-05 09:44:53 +01:00
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
This commit is contained in:
committed by
Dusty Mabe
parent
6363853ea3
commit
aa944b8f6c
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ maipo/
|
||||
.coverage
|
||||
tools/bin
|
||||
.idea
|
||||
bin/
|
||||
|
||||
15
Makefile
15
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
|
||||
@@ -111,7 +120,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
|
||||
|
||||
55
cmd/clean.go
Normal file
55
cmd/clean.go
Normal file
@@ -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/*")
|
||||
}
|
||||
177
cmd/coreos-assembler.go
Normal file
177
cmd/coreos-assembler.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
101
internal/pkg/bashexec/bashexec.go
Normal file
101
internal/pkg/bashexec/bashexec.go
Normal file
@@ -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()
|
||||
}
|
||||
14
internal/pkg/bashexec/bashexec_test.go
Normal file
14
internal/pkg/bashexec/bashexec_test.go
Normal file
@@ -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")
|
||||
}
|
||||
}
|
||||
180
internal/pkg/cosash/cosash.go
Normal file
180
internal/pkg/cosash/cosash.go
Normal file
@@ -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)
|
||||
}
|
||||
@@ -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/*
|
||||
@@ -1,83 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# Usage: coreos-assembler <cmd> ...
|
||||
# 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 secex virtualbox vmware vultr"
|
||||
utility_commands="aws-replicate compress generate-hashlist koji-upload kola remote-build-container 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
|
||||
|
||||
target=/usr/lib/coreos-assembler/cmd-${cmd}
|
||||
if test -x "${target}"; then
|
||||
exec "${target}" "$@"
|
||||
fi
|
||||
|
||||
echo "Unknown command: ${cmd}" 1>&2
|
||||
exit 1
|
||||
Reference in New Issue
Block a user