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:
parent
d53d837e0e
commit
4ea64c3871
5
Makefile
5
Makefile
|
@ -59,7 +59,7 @@ export GOLANGCI_LINT_VERSION := 2.1.0
|
||||||
# Note: Uses the -N -l go compiler options to disable compiler optimizations
|
# Note: Uses the -N -l go compiler options to disable compiler optimizations
|
||||||
# and inlining. Using these build options allows you to subsequently
|
# and inlining. Using these build options allows you to subsequently
|
||||||
# use source debugging tools like delve.
|
# 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
|
# Update nix/nixpkgs.json its latest stable commit
|
||||||
.PHONY: nixpkgs
|
.PHONY: nixpkgs
|
||||||
|
@ -107,6 +107,9 @@ bin/buildah.%: $(SOURCES)
|
||||||
mkdir -p ./bin
|
mkdir -p ./bin
|
||||||
GOOS=$(word 2,$(subst ., ,$@)) GOARCH=$(word 3,$(subst ., ,$@)) $(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ -tags "containers_image_openpgp" ./cmd/buildah
|
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)
|
bin/imgtype: $(SOURCES)
|
||||||
$(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/imgtype/imgtype.go
|
$(GO_BUILD) $(BUILDAH_LDFLAGS) -o $@ $(BUILDFLAGS) ./tests/imgtype/imgtype.go
|
||||||
|
|
||||||
|
|
|
@ -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")
|
|
||||||
}
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/containers/buildah/bind"
|
"github.com/containers/buildah/bind"
|
||||||
|
"github.com/containers/buildah/internal/pty"
|
||||||
"github.com/containers/buildah/util"
|
"github.com/containers/buildah/util"
|
||||||
"github.com/containers/storage/pkg/ioutils"
|
"github.com/containers/storage/pkg/ioutils"
|
||||||
"github.com/containers/storage/pkg/reexec"
|
"github.com/containers/storage/pkg/reexec"
|
||||||
|
@ -217,7 +218,7 @@ func runUsingChrootMain() {
|
||||||
var stderr io.Writer
|
var stderr io.Writer
|
||||||
fdDesc := make(map[int]string)
|
fdDesc := make(map[int]string)
|
||||||
if options.Spec.Process.Terminal {
|
if options.Spec.Process.Terminal {
|
||||||
ptyMasterFd, ptyFd, err := getPtyDescriptors()
|
ptyMasterFd, ptyFd, err := pty.GetPtyDescriptors()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("error opening PTY descriptors: %v", err)
|
logrus.Errorf("error opening PTY descriptors: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//go:build freebsd && cgo
|
//go:build freebsd && cgo
|
||||||
|
|
||||||
package chroot
|
package pty
|
||||||
|
|
||||||
// #include <fcntl.h>
|
// #include <fcntl.h>
|
||||||
// #include <stdlib.h>
|
// #include <stdlib.h>
|
||||||
|
@ -37,7 +37,9 @@ func unlockpt(fd int) error {
|
||||||
return nil
|
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
|
// Create a pseudo-terminal and open the control side
|
||||||
controlFd, err := openpt()
|
controlFd, err := openpt()
|
||||||
if err != nil {
|
if err != nil {
|
|
@ -1,6 +1,6 @@
|
||||||
//go:build linux
|
//go:build linux
|
||||||
|
|
||||||
package chroot
|
package pty
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -11,9 +11,11 @@ import (
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Open a PTY using the /dev/ptmx device. The main advantage of using
|
// GetPtyDescriptors allocates a new pseudoterminal and returns the control and
|
||||||
// this instead of posix_openpt is that it avoids cgo.
|
// pseudoterminal file descriptors. This implementation uses the /dev/ptmx
|
||||||
func getPtyDescriptors() (int, int, error) {
|
// 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.
|
// Create a pseudo-terminal -- open a copy of the master side.
|
||||||
controlFd, err := unix.Open("/dev/ptmx", os.O_RDWR, 0o600)
|
controlFd, err := unix.Open("/dev/ptmx", os.O_RDWR, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
|
@ -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")
|
||||||
|
}
|
|
@ -137,6 +137,7 @@ export BUILDTAGS+=" libtrust_openssl"
|
||||||
%gobuild -o bin/copy ./tests/copy
|
%gobuild -o bin/copy ./tests/copy
|
||||||
%gobuild -o bin/tutorial ./tests/tutorial
|
%gobuild -o bin/tutorial ./tests/tutorial
|
||||||
%gobuild -o bin/inet ./tests/inet
|
%gobuild -o bin/inet ./tests/inet
|
||||||
|
%gobuild -o bin/dumpspec ./tests/dumpspec
|
||||||
%{__make} docs
|
%{__make} docs
|
||||||
|
|
||||||
%install
|
%install
|
||||||
|
@ -148,6 +149,7 @@ cp bin/imgtype %{buildroot}/%{_bindir}/%{name}-imgtype
|
||||||
cp bin/copy %{buildroot}/%{_bindir}/%{name}-copy
|
cp bin/copy %{buildroot}/%{_bindir}/%{name}-copy
|
||||||
cp bin/tutorial %{buildroot}/%{_bindir}/%{name}-tutorial
|
cp bin/tutorial %{buildroot}/%{_bindir}/%{name}-tutorial
|
||||||
cp bin/inet %{buildroot}/%{_bindir}/%{name}-inet
|
cp bin/inet %{buildroot}/%{_bindir}/%{name}-inet
|
||||||
|
cp bin/dumpspec %{buildroot}/%{_bindir}/%{name}-dumpspec
|
||||||
|
|
||||||
rm %{buildroot}%{_datadir}/%{name}/test/system/tools/build/*
|
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}-copy
|
||||||
%{_bindir}/%{name}-tutorial
|
%{_bindir}/%{name}-tutorial
|
||||||
%{_bindir}/%{name}-inet
|
%{_bindir}/%{name}-inet
|
||||||
|
%{_bindir}/%{name}-dumpspec
|
||||||
%{_datadir}/%{name}/test
|
%{_datadir}/%{name}/test
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
|
|
@ -6091,7 +6091,6 @@ _EOF
|
||||||
|
|
||||||
@test "bud with --cgroup-parent" {
|
@test "bud with --cgroup-parent" {
|
||||||
skip_if_rootless_environment
|
skip_if_rootless_environment
|
||||||
skip_if_no_runtime
|
|
||||||
skip_if_chroot
|
skip_if_chroot
|
||||||
|
|
||||||
_prefetch alpine
|
_prefetch alpine
|
||||||
|
@ -6099,24 +6098,18 @@ _EOF
|
||||||
mytmpdir=${TEST_SCRATCH_DIR}/my-dir
|
mytmpdir=${TEST_SCRATCH_DIR}/my-dir
|
||||||
mkdir -p ${mytmpdir}
|
mkdir -p ${mytmpdir}
|
||||||
cat > $mytmpdir/Containerfile << _EOF
|
cat > $mytmpdir/Containerfile << _EOF
|
||||||
from alpine
|
FROM alpine
|
||||||
run cat /proc/self/cgroup
|
RUN .linux.cgroupsPath
|
||||||
_EOF
|
_EOF
|
||||||
|
|
||||||
# with cgroup-parent
|
# with cgroup-parent
|
||||||
run_buildah --cgroup-manager cgroupfs build --cgroupns=host --cgroup-parent test-cgroup -t with-flag \
|
run_buildah --cgroup-manager cgroupfs build --cgroupns=host --cgroup-parent test-cgroup -t with-flag \
|
||||||
$WITH_POLICY_JSON --file ${mytmpdir}/Containerfile .
|
--runtime ${DUMPSPEC_BINARY} $WITH_POLICY_JSON --file ${mytmpdir}/Containerfile .
|
||||||
if is_cgroupsv2; then
|
expect_output --substring "test-cgroup"
|
||||||
expect_output --from="${lines[2]}" "0::/test-cgroup"
|
|
||||||
else
|
|
||||||
expect_output --substring "/test-cgroup"
|
|
||||||
fi
|
|
||||||
# without cgroup-parent
|
# without cgroup-parent
|
||||||
run_buildah --cgroup-manager cgroupfs build -t without-flag \
|
run_buildah --cgroup-manager cgroupfs build -t without-flag \
|
||||||
$WITH_POLICY_JSON --file ${mytmpdir}/Containerfile .
|
--runtime ${DUMPSPEC_BINARY} $WITH_POLICY_JSON --file ${mytmpdir}/Containerfile .
|
||||||
if [ -n "$(grep "test-cgroup" <<< "$output")" ]; then
|
assert "$output" !~ test-cgroup
|
||||||
die "Unexpected cgroup."
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "bud with --cpu-period and --cpu-quota" {
|
@test "bud with --cpu-period and --cpu-quota" {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
@ -8,6 +8,7 @@ IMGTYPE_BINARY=${IMGTYPE_BINARY:-$TEST_SOURCES/../bin/imgtype}
|
||||||
COPY_BINARY=${COPY_BINARY:-$TEST_SOURCES/../bin/copy}
|
COPY_BINARY=${COPY_BINARY:-$TEST_SOURCES/../bin/copy}
|
||||||
TUTORIAL_BINARY=${TUTORIAL_BINARY:-$TEST_SOURCES/../bin/tutorial}
|
TUTORIAL_BINARY=${TUTORIAL_BINARY:-$TEST_SOURCES/../bin/tutorial}
|
||||||
INET_BINARY=${INET_BINARY:-$TEST_SOURCES/../bin/inet}
|
INET_BINARY=${INET_BINARY:-$TEST_SOURCES/../bin/inet}
|
||||||
|
DUMPSPEC_BINARY=${DUMPSPEC_BINARY:-$TEST_SOURCES/../bin/dumpspec}
|
||||||
STORAGE_DRIVER=${STORAGE_DRIVER:-vfs}
|
STORAGE_DRIVER=${STORAGE_DRIVER:-vfs}
|
||||||
PATH=$(dirname ${BASH_SOURCE})/../bin:${PATH}
|
PATH=$(dirname ${BASH_SOURCE})/../bin:${PATH}
|
||||||
OCI=${CI_DESIRED_RUNTIME:-$(${BUILDAH_BINARY} info --format '{{.host.OCIRuntime}}' || command -v runc || command -v crun)}
|
OCI=${CI_DESIRED_RUNTIME:-$(${BUILDAH_BINARY} info --format '{{.host.OCIRuntime}}' || command -v runc || command -v crun)}
|
||||||
|
|
|
@ -9,6 +9,7 @@ environment:
|
||||||
INET_BINARY: /usr/bin/buildah-inet
|
INET_BINARY: /usr/bin/buildah-inet
|
||||||
COPY_BINARY: /usr/bin/buildah-copy
|
COPY_BINARY: /usr/bin/buildah-copy
|
||||||
TUTORIAL_BINARY: /usr/bin/buildah-tutorial
|
TUTORIAL_BINARY: /usr/bin/buildah-tutorial
|
||||||
|
DUMPSPEC_BINARY: /usr/bin/buildah-dumpspec
|
||||||
TMPDIR: /var/tmp
|
TMPDIR: /var/tmp
|
||||||
|
|
||||||
/local/root:
|
/local/root:
|
||||||
|
|
Loading…
Reference in New Issue