Add a dummy "runtime" that just dumps its config file

Add a dummy "runtime" that just dumps its runtime config, either the
entirety of it, or a section of it corresponding to each command line
argument.  Tests can use it to ensure that we set the right thing in the
configuration without also depending on the runtime to do as its asked,
which isn't always something we have control over.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
This commit is contained in:
Nalin Dahyabhai 2025-04-22 17:46:03 -04:00
parent d53d837e0e
commit 4ea64c3871
16 changed files with 664 additions and 32 deletions

View File

@ -59,7 +59,7 @@ export GOLANGCI_LINT_VERSION := 2.1.0
# Note: Uses the -N -l go compiler options to disable compiler optimizations
# and inlining. Using these build options allows you to subsequently
# use source debugging tools like delve.
all: bin/buildah bin/imgtype bin/copy bin/inet bin/tutorial docs
all: bin/buildah bin/imgtype bin/copy bin/inet bin/tutorial bin/dumpspec docs
# Update nix/nixpkgs.json its latest stable commit
.PHONY: nixpkgs
@ -107,6 +107,9 @@ bin/buildah.%: $(SOURCES)
mkdir -p ./bin
GOOS=$(word 2,$(subst ., ,$@)) GOARCH=$(word 3,$(subst ., ,$@)) $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ -tags "containers_image_openpgp" ./cmd/buildah
bin/dumpspec: $(SOURCES)
$(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/dumpspec
bin/imgtype: $(SOURCES)
$(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/imgtype/imgtype.go

View File

@ -1,11 +0,0 @@
//go:build !linux && !(freebsd && cgo)
package chroot
import (
"errors"
)
func getPtyDescriptors() (int, int, error) {
return -1, -1, errors.New("getPtyDescriptors not supported on this platform")
}

View File

@ -18,6 +18,7 @@ import (
"syscall"
"github.com/containers/buildah/bind"
"github.com/containers/buildah/internal/pty"
"github.com/containers/buildah/util"
"github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/reexec"
@ -217,7 +218,7 @@ func runUsingChrootMain() {
var stderr io.Writer
fdDesc := make(map[int]string)
if options.Spec.Process.Terminal {
ptyMasterFd, ptyFd, err := getPtyDescriptors()
ptyMasterFd, ptyFd, err := pty.GetPtyDescriptors()
if err != nil {
logrus.Errorf("error opening PTY descriptors: %v", err)
os.Exit(1)

View File

@ -1,6 +1,6 @@
//go:build freebsd && cgo
package chroot
package pty
// #include <fcntl.h>
// #include <stdlib.h>
@ -37,7 +37,9 @@ func unlockpt(fd int) error {
return nil
}
func getPtyDescriptors() (int, int, error) {
// GetPtyDescriptors allocates a new pseudoterminal and returns the control and
// pseudoterminal file descriptors.
func GetPtyDescriptors() (int, int, error) {
// Create a pseudo-terminal and open the control side
controlFd, err := openpt()
if err != nil {

View File

@ -1,6 +1,6 @@
//go:build linux
package chroot
package pty
import (
"fmt"
@ -11,9 +11,11 @@ import (
"golang.org/x/sys/unix"
)
// Open a PTY using the /dev/ptmx device. The main advantage of using
// this instead of posix_openpt is that it avoids cgo.
func getPtyDescriptors() (int, int, error) {
// GetPtyDescriptors allocates a new pseudoterminal and returns the control and
// pseudoterminal file descriptors. This implementation uses the /dev/ptmx
// device. The main advantage of using this instead of posix_openpt is that it
// avoids cgo.
func GetPtyDescriptors() (int, int, error) {
// Create a pseudo-terminal -- open a copy of the master side.
controlFd, err := unix.Open("/dev/ptmx", os.O_RDWR, 0o600)
if err != nil {

View File

@ -0,0 +1,13 @@
//go:build !linux && !(freebsd && cgo)
package pty
import (
"errors"
)
// GetPtyDescriptors would allocate a new pseudoterminal and return the control and
// pseudoterminal file descriptors, if only it could.
func GetPtyDescriptors() (int, int, error) {
return -1, -1, errors.New("GetPtyDescriptors not supported on this platform")
}

View File

@ -137,6 +137,7 @@ export BUILDTAGS+=" libtrust_openssl"
%gobuild -o bin/copy ./tests/copy
%gobuild -o bin/tutorial ./tests/tutorial
%gobuild -o bin/inet ./tests/inet
%gobuild -o bin/dumpspec ./tests/dumpspec
%{__make} docs
%install
@ -148,6 +149,7 @@ cp bin/imgtype %{buildroot}/%{_bindir}/%{name}-imgtype
cp bin/copy %{buildroot}/%{_bindir}/%{name}-copy
cp bin/tutorial %{buildroot}/%{_bindir}/%{name}-tutorial
cp bin/inet %{buildroot}/%{_bindir}/%{name}-inet
cp bin/dumpspec %{buildroot}/%{_bindir}/%{name}-dumpspec
rm %{buildroot}%{_datadir}/%{name}/test/system/tools/build/*
@ -172,6 +174,7 @@ rm %{buildroot}%{_datadir}/%{name}/test/system/tools/build/*
%{_bindir}/%{name}-copy
%{_bindir}/%{name}-tutorial
%{_bindir}/%{name}-inet
%{_bindir}/%{name}-dumpspec
%{_datadir}/%{name}/test
%changelog

View File

@ -6091,7 +6091,6 @@ _EOF
@test "bud with --cgroup-parent" {
skip_if_rootless_environment
skip_if_no_runtime
skip_if_chroot
_prefetch alpine
@ -6099,24 +6098,18 @@ _EOF
mytmpdir=${TEST_SCRATCH_DIR}/my-dir
mkdir -p ${mytmpdir}
cat > $mytmpdir/Containerfile << _EOF
from alpine
run cat /proc/self/cgroup
FROM alpine
RUN .linux.cgroupsPath
_EOF
# with cgroup-parent
run_buildah --cgroup-manager cgroupfs build --cgroupns=host --cgroup-parent test-cgroup -t with-flag \
$WITH_POLICY_JSON --file ${mytmpdir}/Containerfile .
if is_cgroupsv2; then
expect_output --from="${lines[2]}" "0::/test-cgroup"
else
expect_output --substring "/test-cgroup"
fi
--runtime ${DUMPSPEC_BINARY} $WITH_POLICY_JSON --file ${mytmpdir}/Containerfile .
expect_output --substring "test-cgroup"
# without cgroup-parent
run_buildah --cgroup-manager cgroupfs build -t without-flag \
$WITH_POLICY_JSON --file ${mytmpdir}/Containerfile .
if [ -n "$(grep "test-cgroup" <<< "$output")" ]; then
die "Unexpected cgroup."
fi
--runtime ${DUMPSPEC_BINARY} $WITH_POLICY_JSON --file ${mytmpdir}/Containerfile .
assert "$output" !~ test-cgroup
}
@test "bud with --cpu-period and --cpu-quota" {

475
tests/dumpspec/dumpspec.go Normal file
View File

@ -0,0 +1,475 @@
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"unicode"
"github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/reexec"
rspec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
// use defined names for our various commands. we absolutely don't support
// everything that an actual functional runtime would, and have no intention of
// expanding to do so
type modeType string
const (
modeCreate = modeType("create")
modeStart = modeType("start")
modeState = modeType("state")
modeKill = modeType("kill")
modeDelete = modeType("delete")
subprocName = "dumpspec-subproc"
)
// signalsByName is a guess at which signals we'd be asked to send to a child
// process, currently restricted to the subset defined across all of our
// targets
var signalsByName = map[string]syscall.Signal{
"SIGABRT": syscall.SIGABRT,
"SIGALRM": syscall.SIGALRM,
"SIGBUS": syscall.SIGBUS,
"SIGFPE": syscall.SIGFPE,
"SIGHUP": syscall.SIGHUP,
"SIGILL": syscall.SIGILL,
"SIGINT": syscall.SIGINT,
"SIGKILL": syscall.SIGKILL,
"SIGPIPE": syscall.SIGPIPE,
"SIGQUIT": syscall.SIGQUIT,
"SIGSEGV": syscall.SIGSEGV,
"SIGTERM": syscall.SIGTERM,
"SIGTRAP": syscall.SIGTRAP,
}
var (
globalArgs struct {
debug bool
cgroupManager string
log string
logFormat string
logLevel string
root string
systemdCgroup bool
rootless bool
}
createArgs struct {
bundleDir string
configFile string
consoleSocket string
pidFile string
noPivot bool
noNewKeyring bool
preserveFds int
}
stateArgs struct {
all bool
pid int
regex string
}
killArgs struct {
all bool
pid int
regex string
signal int
}
deleteArgs struct {
force bool
regex string
}
)
func main() {
if reexec.Init() {
return
}
if len(os.Args) < 2 {
return
}
var container, containerID, containerDir string
mainCommand := cobra.Command{
Use: "dumpspec",
Short: "fake OCI runtime",
PersistentPreRunE: func(_ *cobra.Command, args []string) error {
tmpdir, ok := os.LookupEnv("XDG_RUNTIME_DIR")
if !ok {
tmpdir = filepath.Join(os.TempDir(), strconv.Itoa(os.Getuid()))
}
if globalArgs.root != "" {
tmpdir = globalArgs.root
}
tmpdir = filepath.Join(tmpdir, "dumpspec")
if err := os.MkdirAll(tmpdir, 0o700); err != nil && !errors.Is(err, os.ErrExist) {
return fmt.Errorf("ensuring that %q exists: %w", tmpdir, err)
}
if len(args) > 0 {
// this is the first arg for all of the commands that we care about
container = args[0]
}
containerID = mapToContainerID(container)
containerDir = filepath.Join(tmpdir, containerID)
return nil
},
}
mainFlags := mainCommand.PersistentFlags()
mainFlags.BoolVar(&globalArgs.debug, "debug", false, "log for debugging")
mainFlags.BoolVar(&globalArgs.systemdCgroup, "systemd-cgroup", false, "use systemd for handling cgroups")
mainFlags.BoolVar(&globalArgs.rootless, "rootless", false, "ignore some settings to that conflict with rootless operation")
mainFlags.StringVar(&globalArgs.cgroupManager, "cgroup-manager", "cgroupfs", "method for managing cgroups")
mainFlags.StringVar(&globalArgs.log, "log", "", "logging destination")
mainFlags.StringVar(&globalArgs.logFormat, "log-format", "", "logging format specifier")
mainFlags.StringVar(&globalArgs.logLevel, "log-level", "", "logging level")
rootUsage := "root `directory` of runtime data"
rootDefault := ""
if xdgRuntimeDir, ok := os.LookupEnv("XDG_RUNTIME_DIR"); ok {
rootUsage += " (default $XDG_RUNTIME_DIR)"
rootDefault = xdgRuntimeDir
}
mainFlags.StringVar(&globalArgs.root, "root", rootDefault, rootUsage)
createCommand := &cobra.Command{
Use: string(modeCreate),
Args: cobra.ExactArgs(1),
Short: "create a ready-to-start container process",
RunE: func(_ *cobra.Command, _ []string) error {
if err := os.MkdirAll(containerDir, 0o700); err != nil {
return fmt.Errorf("creating container directory: %w", err)
}
configFile := createArgs.configFile
if configFile == "" {
configFile = filepath.Join(createArgs.bundleDir, "config.json")
}
config, err := os.ReadFile(configFile)
if err != nil {
return fmt.Errorf("reading runtime configuration: %w", err)
}
var spec rspec.Spec
if err := json.Unmarshal(config, &spec); err != nil {
return fmt.Errorf("parsing runtime configuration: %w", err)
}
if err := os.WriteFile(filepath.Join(containerDir, "config.json"), config, 0o600); err != nil {
return fmt.Errorf("saving copy of runtime configuration: %w", err)
}
state := rspec.State{
Version: rspec.Version,
ID: container,
Bundle: createArgs.bundleDir,
}
stateBytes, err := json.Marshal(state)
if err != nil {
return fmt.Errorf("encoding initial runtime state: %w", err)
}
if err := os.WriteFile(filepath.Join(containerDir, "state"), stateBytes, 0o600); err != nil {
return fmt.Errorf("writing initial runtime state: %w", err)
}
pr, pw, err := os.Pipe()
if err != nil {
return fmt.Errorf("internal error: %w", err)
}
defer pr.Close()
cmd := getStarter(containerDir, createArgs.consoleSocket, createArgs.pidFile, spec, pw)
if err := cmd.Start(); err != nil {
return fmt.Errorf("internal error: %w", err)
}
pw.Close()
ready, err := io.ReadAll(pr)
if err != nil {
return fmt.Errorf("waiting for child to start: %w", err)
}
if strings.TrimSpace(string(ready)) != "OK" {
return fmt.Errorf("unexpected child status %q", string(ready))
}
return nil
},
}
createFlags := createCommand.Flags()
createFlags.StringVarP(&createArgs.bundleDir, "bundle", "b", "", "`directory` containing config.json")
createFlags.StringVarP(&createArgs.configFile, "config", "f", "", "`path` to config.json")
createFlags.StringVar(&createArgs.consoleSocket, "console-socket", "", "socket `path` for passing PTY")
createFlags.StringVar(&createArgs.pidFile, "pid-file", "", "`path` in which to store child PID")
createFlags.BoolVar(&createArgs.noPivot, "no-pivot", false, "use chroot() instead of pivot_root()")
createFlags.BoolVar(&createArgs.noNewKeyring, "no-new-keyring", false, "don't create a new keyring")
mainCommand.AddCommand(createCommand)
startCommand := &cobra.Command{
Use: string(modeStart),
Args: cobra.ExactArgs(1),
Short: "start a previously-created container process",
RunE: func(_ *cobra.Command, _ []string) error {
if err := ioutils.AtomicWriteFile(filepath.Join(containerDir, "start"), []byte("start"), 0o600); err != nil {
return fmt.Errorf("writing start file: %w", err)
}
return nil
},
}
mainCommand.AddCommand(startCommand)
stateCommand := &cobra.Command{
Use: string(modeState),
Args: cobra.ExactArgs(1),
Short: "poll the state of a container process",
RunE: func(_ *cobra.Command, _ []string) error {
stateFile, err := os.Open(filepath.Join(containerDir, "state"))
if err != nil {
return err
}
defer stateFile.Close()
if _, err := io.Copy(os.Stdout, stateFile); err != nil {
return fmt.Errorf("copying state file: %w", err)
}
return nil
},
}
stateFlags := stateCommand.Flags()
stateFlags.BoolVarP(&stateArgs.all, "all", "a", false, "start all containers")
stateFlags.IntVar(&stateArgs.pid, "pid", 0, "start container by `pid`")
stateFlags.StringVarP(&stateArgs.regex, "regex", "r", "", "start containers with IDs matching a `regex`")
mainCommand.AddCommand(stateCommand)
killCommand := &cobra.Command{
Use: string(modeKill),
Args: cobra.RangeArgs(1, 2),
Short: "signal/kill a container process",
RunE: func(_ *cobra.Command, args []string) error {
if len(args) > 1 {
signalString := args[1]
signalNumber, err := strconv.Atoi(signalString)
if err != nil {
n, ok := signalsByName[signalString]
if !ok {
n, ok = signalsByName["SIG"+signalString]
if !ok {
return fmt.Errorf("%v: unrecognized signal %q", os.Args, signalString)
}
}
signalNumber = int(n)
}
killArgs.signal = signalNumber
}
if err := ioutils.AtomicWriteFile(filepath.Join(containerDir, "kill"), []byte(strconv.Itoa(killArgs.signal)), 0o600); err != nil {
return fmt.Errorf("writing exit status file: %w", err)
}
return nil
},
}
killFlags := killCommand.Flags()
killFlags.BoolVarP(&killArgs.all, "all", "a", false, "signal/kill all containers")
killFlags.IntVar(&killArgs.pid, "pid", 0, "signal/kill container by `pid`")
killFlags.StringVarP(&killArgs.regex, "regex", "r", "", "signal/kill containers with IDs matching a `regex`")
mainCommand.AddCommand(killCommand)
deleteCommand := &cobra.Command{
Use: string(modeDelete),
Args: cobra.ExactArgs(1),
Short: "delete a container process",
RunE: func(_ *cobra.Command, _ []string) error {
if err := os.RemoveAll(containerDir); err != nil {
return fmt.Errorf("removing container directory: %w", err)
}
return nil
},
}
deleteFlags := deleteCommand.Flags()
deleteFlags.StringVarP(&deleteArgs.regex, "regex", "r", "", "delete containers with IDs matching a `regex`")
deleteFlags.BoolVarP(&deleteArgs.force, "force", "f", false, "forcibly stop containers which are not stopped")
mainCommand.AddCommand(deleteCommand)
err := mainCommand.Execute()
if err != nil {
logrus.Fatal(err)
os.Exit(1)
}
os.Exit(0)
}
func mapToContainerID(container string) string {
var encoder strings.Builder
for _, c := range container {
if unicode.IsLetter(c) || unicode.IsNumber(c) {
if _, err := encoder.WriteRune(c); err != nil {
logrus.Fatalf("%v: encoding container ID: %q: %v", os.Args, c, err)
}
} else {
if _, err := encoder.WriteString(strconv.Itoa(int(c))); err != nil {
logrus.Fatalf("%v: encoding container ID: %q: %v", os.Args, c, err)
}
}
}
return encoder.String()
}
func waitForFile(dirname, basename string) string {
waitedFile := filepath.Join(dirname, basename)
for {
if _, err := os.Stat(dirname); err != nil {
logrus.Fatalf("%v: %v", os.Args, err)
}
st, err := os.Stat(waitedFile)
if err != nil && !errors.Is(err, os.ErrNotExist) {
logrus.Fatalf("%v: %v", os.Args, err)
}
if err != nil || st.Size() == 0 {
time.Sleep(100 * time.Millisecond)
continue
}
contents, err := os.ReadFile(waitedFile)
if err != nil {
logrus.Fatalf("%v: %v", os.Args, err)
}
text := strings.TrimSpace(string(contents))
return text
}
}
func init() {
reexec.Register(subprocName, subproc)
}
func subproc() {
mainCommand := cobra.Command{
Use: "dumpspec",
Short: "fake OCI runtime",
Long: "dumpspec containerDir consoleSocket pidFile [spec ...]",
Args: cobra.ExactArgs(3),
RunE: func(_ *cobra.Command, args []string) error {
dir := args[0]
consoleSocket := args[1]
pidFile := args[2]
config, err := os.ReadFile(filepath.Join(dir, "config.json"))
if err != nil {
return fmt.Errorf("reading runtime configuration: %w", err)
}
var spec rspec.Spec
if err := json.Unmarshal(config, &spec); err != nil {
return fmt.Errorf("parsing runtime configuration: %w", err)
}
stateBytes, err := os.ReadFile(filepath.Join(dir, "state"))
if err != nil {
return fmt.Errorf("reading initial state : %w", err)
}
var state rspec.State
if err := json.Unmarshal(stateBytes, &state); err != nil {
return fmt.Errorf("parsing initial state: %w", err)
}
saveState := func() error {
stateBytes, err := json.Marshal(state)
if err != nil {
return fmt.Errorf("encoding updated state: %w", err)
}
err = ioutils.AtomicWriteFile(filepath.Join(dir, "state"), stateBytes, 0o600)
if err != nil {
return fmt.Errorf("writing updated state: %w", err)
}
return nil
}
output := io.Writer(os.Stdout)
if pidFile != "" {
if err := ioutils.AtomicWriteFile(pidFile, []byte(strconv.Itoa(os.Getpid())), 0o600); err != nil {
return fmt.Errorf("writing pid file %q: %w", pidFile, err)
}
}
state.Pid = os.Getpid()
state.Status = rspec.StateCreated
if err := saveState(); err != nil {
return err
}
if consoleSocket != "" {
if output, err = sendConsoleDescriptor(consoleSocket); err != nil {
return fmt.Errorf("sending terminal control fd to parent process: %w", err)
}
}
ok := os.NewFile(3, "startup status pipe")
fmt.Fprintf(ok, "OK")
ok.Close()
start := waitForFile(dir, "start")
if start != "start" {
return fmt.Errorf("unexpected start indicator %q", start)
}
state.Status = rspec.StateRunning
if err := saveState(); err != nil {
return err
}
if spec.Process == nil || len(spec.Process.Args) == 0 {
if _, err := io.Copy(output, bytes.NewReader(config)); err != nil {
return fmt.Errorf("writing configuration: %w", err)
}
} else {
for _, query := range spec.Process.Args {
var data any
if err := json.Unmarshal(config, &data); err != nil {
return fmt.Errorf("parsing runtime configuration: %w", err)
}
path := strings.Split(query, ".")
for i, component := range path {
if component == "" {
continue
}
pathSoFar := strings.Join(path[:i], ".")
if data == nil {
return fmt.Errorf("unable to descend into %q after %q", component, pathSoFar)
}
if m, ok := data.(map[string]any); ok {
data = m[component]
} else if s, ok := data.([]any); ok {
i, err := strconv.Atoi(component)
if err != nil {
return fmt.Errorf("%q is not numeric while indexing slice at %q", component, pathSoFar)
}
data = s[i]
} else {
return fmt.Errorf("unable to descend into %q after %q", component, pathSoFar)
}
}
final, err := json.Marshal(data)
if err != nil {
return fmt.Errorf("encoding query result: %w", err)
}
if len(final) == 0 || final[len(final)-1] != '\n' {
final = append(final, byte('\n'))
}
if _, err := io.Copy(output, bytes.NewReader(final)); err != nil {
return fmt.Errorf("writing configuration subset %q: %w", query, err)
}
}
}
state.Status = rspec.StateStopped
if err := saveState(); err != nil {
return err
}
return nil
},
}
err := mainCommand.Execute()
if err != nil {
logrus.Fatal(err)
os.Exit(1)
}
os.Exit(0)
}

View File

@ -0,0 +1,41 @@
package main
import (
"os"
"slices"
"syscall"
"github.com/containers/storage/pkg/unshare"
rspec "github.com/opencontainers/runtime-spec/specs-go"
)
func getStarter(containerDir, consoleSocket, pidFile string, spec rspec.Spec, extraFile *os.File) interface{ Start() error } {
cmd := unshare.Command(subprocName, containerDir, consoleSocket, pidFile)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if spec.Linux != nil {
for _, ns := range spec.Linux.Namespaces {
switch ns.Type {
case rspec.UserNamespace:
cmd.UnshareFlags |= syscall.CLONE_NEWUSER
case rspec.NetworkNamespace: // caller is expecting to configure networking for this process's network namespace
cmd.UnshareFlags |= syscall.CLONE_NEWNET
case rspec.MountNamespace:
cmd.UnshareFlags |= syscall.CLONE_NEWNS
case rspec.IPCNamespace:
cmd.UnshareFlags |= syscall.CLONE_NEWIPC
case rspec.UTSNamespace:
cmd.UnshareFlags |= syscall.CLONE_NEWUTS
case rspec.CgroupNamespace:
cmd.UnshareFlags |= syscall.CLONE_NEWCGROUP
}
}
cmd.UidMappings = slices.Clone(spec.Linux.UIDMappings)
cmd.GidMappings = slices.Clone(spec.Linux.GIDMappings)
}
if extraFile != nil {
cmd.ExtraFiles = append([]*os.File{extraFile}, cmd.ExtraFiles...)
}
return cmd
}

View File

@ -0,0 +1,21 @@
//go:build !linux
package main
import (
"os"
"os/exec"
rspec "github.com/opencontainers/runtime-spec/specs-go"
)
func getStarter(containerDir, consoleSocket, pidFile string, _ rspec.Spec, extraFile *os.File) interface{ Start() error } {
cmd := exec.Command(subprocName, containerDir, consoleSocket, pidFile)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if extraFile != nil {
cmd.ExtraFiles = append([]*os.File{extraFile}, cmd.ExtraFiles...)
}
return cmd
}

View File

@ -0,0 +1,12 @@
//go:build windows
package main
import (
"errors"
"os"
)
func sendConsoleDescriptor(consoleSocket string) (*os.File, error) {
return nil, errors.New("unable to transport pseudoterminal descriptors")
}

View File

@ -0,0 +1,41 @@
//go:build !windows
package main
import (
"fmt"
"net"
"os"
"github.com/containers/buildah/internal/pty"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
func sendConsoleDescriptor(consoleSocket string) (*os.File, error) {
closePty := true
control, pty, err := pty.GetPtyDescriptors()
if err != nil {
return nil, fmt.Errorf("allocating pseudo-terminal: %w", err)
}
defer unix.Close(control)
defer func() {
if closePty {
if err := unix.Close(pty); err != nil {
logrus.Errorf("closing pty descriptor %d: %v", pty, err)
}
}
}()
socketReceiver, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: consoleSocket, Net: "unix"})
if err != nil {
return nil, fmt.Errorf("allocating pseudo-terminal: %w", err)
}
defer socketReceiver.Close()
rights := unix.UnixRights(control)
_, _, err = socketReceiver.WriteMsgUnix(nil, rights, nil)
if err != nil {
return nil, fmt.Errorf("sending terminal control fd to parent process: %w", err)
}
closePty = false
return os.NewFile(uintptr(pty), "controlling terminal"), nil
}

34
tests/dumpspec/notes.md Normal file
View File

@ -0,0 +1,34 @@
Global flags:
crun: --cgroup-manager=MANAGER --debug --log=FILE --log-format={text|json} --log-level --root=DIR --rootless={true|false|auto} --systemd-cgroup
runc: --debug --log=FILE --log-format={text|json} --root=DIR --systemd-cgroup --rootless={true|false|auto}
create [-b|--bundle dir] [--console-socket[=]path] [--pid-file[=]path] [--no-pivot] [--preserve-fds[=]N] containerID
runc: [--pidfd-socket=path] [--no-new-keyring]
crun: [-f|--config file] [--no-subreaper (ignored)] [--no-new-keyring]
runsc: [--pidfd-socket=path]
* Start keeping track of containerID under --root or $XDG_RUNTIME_DIR/$runtimeName
* If console socket given, allocate a pseudoterminal, connect to it, and pass a TTY descriptor.
* If not, pass stdio down directly.
* Prepare, but have babysitter wait before starting process.
start containerID
* Start process connected to stdio or terminal.
state containerID
crun: [-a|--all] [-r|--regex regex]
runsc: [-all|--all] [-pid int (in parent pid namespace)]
* Output a JSON-encoded github.com/opencontainers/runtime-spec/specs-go.State value on stdout.
kill containerID [signal]
crun: [-a|--all] [-r|--regex regex]
runsc: [-all|--all] [-pid int (in parent pid namespace)]
* Send signal to process tree.
delete containerID
runc: [-f|--force (SIGKILL first if need be)]
crun: [-f|--force (SIGKILL first if need be)] [-r|--regex regex]
runsc: [-force|--force]
runc: checkpoint events exec features list pause ps resume restore run spec state update
crun: checkpoint exec features list pause ps resume restore run spec state update
runsc: checkpoint do events exec flags list pause port-forward ps restore resume run spec state wait

View File

@ -8,6 +8,7 @@ IMGTYPE_BINARY=${IMGTYPE_BINARY:-$TEST_SOURCES/../bin/imgtype}
COPY_BINARY=${COPY_BINARY:-$TEST_SOURCES/../bin/copy}
TUTORIAL_BINARY=${TUTORIAL_BINARY:-$TEST_SOURCES/../bin/tutorial}
INET_BINARY=${INET_BINARY:-$TEST_SOURCES/../bin/inet}
DUMPSPEC_BINARY=${DUMPSPEC_BINARY:-$TEST_SOURCES/../bin/dumpspec}
STORAGE_DRIVER=${STORAGE_DRIVER:-vfs}
PATH=$(dirname ${BASH_SOURCE})/../bin:${PATH}
OCI=${CI_DESIRED_RUNTIME:-$(${BUILDAH_BINARY} info --format '{{.host.OCIRuntime}}' || command -v runc || command -v crun)}

View File

@ -9,6 +9,7 @@ environment:
INET_BINARY: /usr/bin/buildah-inet
COPY_BINARY: /usr/bin/buildah-copy
TUTORIAL_BINARY: /usr/bin/buildah-tutorial
DUMPSPEC_BINARY: /usr/bin/buildah-dumpspec
TMPDIR: /var/tmp
/local/root: