Skip to content

Commit

Permalink
Make /usr/bin/cosa a Go program
Browse files Browse the repository at this point in the history
This creates an initial skeleton for Go code at the very toplevel
of the project.

What is currently the `coreos-assembler` shell script entrypoint is
changed to be embedded via Go file embedding into `/usr/bin/cosa`.
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.

There's an embryonic `bashexec` internal Go module that is designed
to help with this.

Closes: coreos#2821
  • Loading branch information
cgwalters committed Jul 6, 2022
1 parent 46d5bc9 commit 58e6b2c
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 80 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ maipo/
.coverage
tools/bin
.idea
bin/
10 changes: 7 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ PYIGNORE ?= E128,E241,E402,E501,E722,W503,W504

MANTLE_BINARIES := ore kola plume

all: tools mantle gangplank
all: bin/cosa 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)
Expand All @@ -30,6 +30,10 @@ else ifeq ($(GOARCH),aarch64)
GOARCH="arm64"
endif

bin/cosa:
go build -mod vendor -o $@ cmd/cosa.go
.PHONY: bin/cosa

.%.shellchecked: %
./tests/check_one.sh $< $@

Expand Down Expand Up @@ -114,9 +118,9 @@ 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/cosa $(DESTDIR)$(PREFIX)/bin/
ln -sf ../lib/coreos-assembler/cp-reflink $(DESTDIR)$(PREFIX)/bin/
ln -sf coreos-assembler $(DESTDIR)$(PREFIX)/bin/cosa
ln -sf cosa $(DESTDIR)$(PREFIX)/bin/coreos-assembler
install -d $(DESTDIR)$(PREFIX)/lib/coreos-assembler/tests/kola
cd tools && $(MAKE) install DESTDIR=$(DESTDIR)
cd mantle && $(MAKE) install DESTDIR=$(DESTDIR)
Expand Down
137 changes: 137 additions & 0 deletions cmd/cosa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package main

import (
_ "embed"
"fmt"
"io/ioutil"
"os"
"os/exec"
"sort"
"strings"
)

// 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 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 {
var cmd string
if len(argv) > 0 {
cmd = argv[0]
argv = argv[1:]
}

if cmd == "" {
printUsage()
os.Exit(1)
}

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
return c.Run()
}

func initializeGlobalState() 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("export 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 {
return err
}
user = strings.TrimSpace(string(b))
os.Setenv("USER", user)
}

// 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 {
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 err
}
defer f.Close()
id := os.Getuid()
buf := fmt.Sprintf("%s:x:%d:0:%s user:%s:/sbin/nologin", user, id, user, home)
if _, err = f.WriteString(buf); err != nil {
return err
}
}

return nil
}

func sanityCheckEnvironment() error {
// https://github.com/containers/libpod/issues/1448
if _, err := os.Stat("/sys/fs/selinux/status"); err == nil {
return fmt.Errorf("/sys/fs/selinux appears to be mounted but should not be")
}

return nil
}

func main() {
sanityCheckEnvironment()
initializeGlobalState()

err := run(os.Args[1:])
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/coreos/coreos-assembler

go 1.15
60 changes: 60 additions & 0 deletions internal/pkg/bashexec/bashexec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package bashexec

import (
"fmt"
"io"
"os"
"os/exec"
"strings"
)

// 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) {
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
}

fullargs := append([]string{"-c", ". /proc/self/fd/3", name}, args...)
cmd := exec.Command("/bin/bash", fullargs...)
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
}
77 changes: 0 additions & 77 deletions src/coreos-assembler

This file was deleted.

0 comments on commit 58e6b2c

Please sign in to comment.