cli/connhelper: remove dependency on pkg/process

This package will not be included in the api or client modules, and
we're currently only using a single function of it, and only the
unix implementation, so let's fork it for now (although the package
may be moved to moby/sys).

This removes the last dependency on github.com/docker/docker.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-08-05 16:25:40 +02:00
parent fcfaa8daeb
commit 2abcbf842f
13 changed files with 46 additions and 2883 deletions

View File

@ -4,12 +4,17 @@ package commandconn
import (
"context"
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"strconv"
"syscall"
"testing"
"time"
"github.com/docker/docker/pkg/process"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@ -51,16 +56,16 @@ func TestCloseRunningCommand(t *testing.T) {
c, err := New(ctx, "sh", "-c", "while true; do sleep 1; done")
assert.NilError(t, err)
cmdConn := c.(*commandConn)
assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
assert.Check(t, processAlive(cmdConn.cmd.Process.Pid))
n, err := c.Write([]byte("hello"))
assert.Check(t, is.Equal(len("hello"), n))
assert.NilError(t, err)
assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
assert.Check(t, processAlive(cmdConn.cmd.Process.Pid))
err = cmdConn.Close()
assert.NilError(t, err)
assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid))
done <- struct{}{}
}()
@ -79,7 +84,7 @@ func TestCloseTwice(t *testing.T) {
c, err := New(ctx, "sh", "-c", "echo hello; sleep 1; exit 0")
assert.NilError(t, err)
cmdConn := c.(*commandConn)
assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
assert.Check(t, processAlive(cmdConn.cmd.Process.Pid))
b := make([]byte, 32)
n, err := c.Read(b)
@ -88,11 +93,11 @@ func TestCloseTwice(t *testing.T) {
err = cmdConn.Close()
assert.NilError(t, err)
assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid))
err = cmdConn.Close()
assert.NilError(t, err)
assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid))
done <- struct{}{}
}()
@ -111,7 +116,7 @@ func TestEOFTimeout(t *testing.T) {
c, err := New(ctx, "sh", "-c", "sleep 20")
assert.NilError(t, err)
cmdConn := c.(*commandConn)
assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
assert.Check(t, processAlive(cmdConn.cmd.Process.Pid))
cmdConn.stdout = mockStdoutEOF{}
@ -148,7 +153,7 @@ func TestCloseWhileWriting(t *testing.T) {
c, err := New(ctx, "sh", "-c", "while true; do sleep 1; done")
assert.NilError(t, err)
cmdConn := c.(*commandConn)
assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
assert.Check(t, processAlive(cmdConn.cmd.Process.Pid))
writeErrC := make(chan error)
go func() {
@ -164,7 +169,7 @@ func TestCloseWhileWriting(t *testing.T) {
err = c.Close()
assert.NilError(t, err)
assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid))
writeErr := <-writeErrC
assert.ErrorContains(t, writeErr, "file already closed")
@ -176,7 +181,7 @@ func TestCloseWhileReading(t *testing.T) {
c, err := New(ctx, "sh", "-c", "while true; do sleep 1; done")
assert.NilError(t, err)
cmdConn := c.(*commandConn)
assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
assert.Check(t, processAlive(cmdConn.cmd.Process.Pid))
readErrC := make(chan error)
go func() {
@ -193,8 +198,37 @@ func TestCloseWhileReading(t *testing.T) {
err = cmdConn.Close()
assert.NilError(t, err)
assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
assert.Check(t, !processAlive(cmdConn.cmd.Process.Pid))
readErr := <-readErrC
assert.Check(t, is.ErrorIs(readErr, fs.ErrClosed))
}
// processAlive returns true if a process with a given pid is running. It only considers
// positive PIDs; 0 (all processes in the current process group), -1 (all processes
// with a PID larger than 1), and negative (-n, all processes in process group
// "n") values for pid are never considered to be alive.
//
// It was forked from https://github.com/moby/moby/blob/v28.3.3/pkg/process/process_unix.go#L17-L42
func processAlive(pid int) bool {
if pid < 1 {
return false
}
switch runtime.GOOS {
case "darwin":
// OS X does not have a proc filesystem. Use kill -0 pid to judge if the
// process exists. From KILL(2): https://www.freebsd.org/cgi/man.cgi?query=kill&sektion=2&manpath=OpenDarwin+7.2.1
//
// Sig may be one of the signals specified in sigaction(2) or it may
// be 0, in which case error checking is performed but no signal is
// actually sent. This can be used to check the validity of pid.
err := syscall.Kill(pid, 0)
// Either the PID was found (no error), or we get an EPERM, which means
// the PID exists, but we don't have permissions to signal it.
return err == nil || errors.Is(err, syscall.EPERM)
default:
_, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid)))
return err == nil
}
}