Skip to content

Commit

Permalink
resolves #37 add initial docker exec support
Browse files Browse the repository at this point in the history
  • Loading branch information
slonopotamus committed Jan 27, 2024
1 parent 7948e15 commit 743ba9a
Show file tree
Hide file tree
Showing 4 changed files with 354 additions and 167 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
= Changelog
:icons: font

== Unreleased

- Add initial `docker exec` support (#37)

== 0.0.4

- Add support for container exit times (#21)
Expand Down
57 changes: 29 additions & 28 deletions containerd/container.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package containerd

import (
"github.com/containerd/containerd/v2/api/types/task"
"github.com/containerd/containerd/v2/errdefs"
"github.com/containerd/containerd/v2/mount"
"github.com/containerd/containerd/v2/oci"
"github.com/hashicorp/go-multierror"
"golang.org/x/sys/unix"
"os"
"os/exec"
"sync"
"time"
)

const unmountFlags = unix.MNT_FORCE
Expand All @@ -20,28 +18,31 @@ type container struct {
bundlePath string
rootfs string
dnsSocketPath string
io stdio
console *os.File

mu sync.Mutex
cmd *exec.Cmd
waitblock chan struct{}
status task.Status
exitStatus uint32
exitedAt time.Time

mu sync.Mutex

// primary is the primary process for the container.
// The lifetime of the container is tied to this process.
primary managedProcess

// auxiliary is a map of additional processes that run in the jail.
auxiliary map[string]*managedProcess
}

func (c *container) destroy() (retErr error) {
if err := c.io.Close(); err != nil {
retErr = multierror.Append(retErr, err)
}
c.mu.Lock()
defer c.mu.Unlock()

if c.console != nil {
if err := c.console.Close(); err != nil {
for _, p := range c.auxiliary {
if err := p.destroy(); err != nil {
retErr = multierror.Append(retErr, err)
}
}

if err := c.primary.destroy(); err != nil {
retErr = multierror.Append(retErr, err)
}

// Remove socket file to avoid continuity "failed to create irregular file" error during multiple Dockerfile `RUN` steps
_ = os.Remove(c.dnsSocketPath)

Expand All @@ -52,23 +53,23 @@ func (c *container) destroy() (retErr error) {
return
}

func (c *container) setStatusL(status task.Status) {
func (c *container) getProcessL(execID string) (*managedProcess, error) {
c.mu.Lock()
defer c.mu.Unlock()

c.status = status
return c.getProcess(execID)
}

func (c *container) getStatusL() task.Status {
c.mu.Lock()
defer c.mu.Unlock()
func (c *container) getProcess(execID string) (*managedProcess, error) {
if execID == "" {
return &c.primary, nil
}

return c.status
}
p := c.auxiliary[execID]

func (c *container) getConsoleL() *os.File {
c.mu.Lock()
defer c.mu.Unlock()
if p == nil {
return nil, errdefs.ToGRPCf(errdefs.ErrNotFound, "exec not found: %s", execID)
}

return c.console
return p, nil
}
133 changes: 133 additions & 0 deletions containerd/managed_process.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package containerd

import (
"context"
"github.com/containerd/containerd/v2/api/types/task"
"github.com/creack/pty"
"github.com/hashicorp/go-multierror"
"github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
"io"
"os"
"os/exec"
"sync"
"syscall"
"time"
)

type managedProcess struct {
spec *specs.Process
io stdio
console *os.File
mu sync.Mutex
cmd *exec.Cmd
waitblock chan struct{}
status task.Status
exitStatus uint32
exitedAt time.Time
}

func (p *managedProcess) getConsoleL() *os.File {
p.mu.Lock()
defer p.mu.Unlock()

return p.console
}

func (p *managedProcess) destroy() (retErr error) {
// TODO: Do we care about error?
_ = p.kill(syscall.SIGKILL)

if err := p.io.Close(); err != nil {
retErr = multierror.Append(retErr, err)
}

if p.console != nil {
if err := p.console.Close(); err != nil {
retErr = multierror.Append(retErr, err)
}
}

if p.status != task.Status_STOPPED {
p.status = task.Status_STOPPED
p.exitedAt = time.Now()
p.exitStatus = uint32(syscall.SIGKILL)
}

return
}

func (p *managedProcess) kill(signal syscall.Signal) error {
if p.cmd != nil {
if process := p.cmd.Process; p != nil {
return unix.Kill(-process.Pid, signal)
}
}

return nil
}

func (p *managedProcess) setup(ctx context.Context, rootfs string, stdin string, stdout string, stderr string) error {
var err error

p.io, err = setupIO(ctx, stdin, stdout, stderr)
if err != nil {
return err
}

if len(p.spec.Args) <= 0 {
// TODO: How to handle this properly?
p.spec.Args = []string{"/bin/sh"}
// return fmt.Errorf("args must not be empty")
}

p.cmd = exec.Command(p.spec.Args[0])
p.cmd.Args = p.spec.Args
p.cmd.Dir = p.spec.Cwd
p.cmd.Env = p.spec.Env
p.cmd.SysProcAttr = &syscall.SysProcAttr{
Chroot: rootfs,
Credential: &syscall.Credential{
Uid: p.spec.User.UID,
Gid: p.spec.User.GID,
},
}

return nil
}

func (p *managedProcess) start() (err error) {
if p.spec.Terminal {
// TODO: I'd like to use containerd/console package instead
// But see https://github.com/containerd/console/issues/79
var consoleSize *pty.Winsize
if p.spec.ConsoleSize != nil {
consoleSize = &pty.Winsize{
Cols: uint16(p.spec.ConsoleSize.Width),
Rows: uint16(p.spec.ConsoleSize.Height),
}
}

p.console, err = pty.StartWithSize(p.cmd, consoleSize)
if err != nil {
return err
}

go io.Copy(p.console, p.io.stdin)
go io.Copy(p.io.stdout, p.console)
} else {
p.cmd.SysProcAttr.Setpgid = true
p.cmd.Stdin = p.io.stdin
p.cmd.Stdout = p.io.stdout
p.cmd.Stderr = p.io.stderr

err = p.cmd.Start()
if err != nil {
return err
}
}

p.status = task.Status_RUNNING

return nil
}
Loading

0 comments on commit 743ba9a

Please sign in to comment.