Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4debf411d1 | |||
| 5e6ce1bde1 | |||
| 5428301e3f | |||
| 1cbc218c05 | |||
| 2f6b5ada71 | |||
| d8e07c9c47 | |||
| 5f1b610fc3 | |||
| c105cd3ac2 | |||
| 62b2963b80 | |||
| 71f2b0d109 | |||
| 617bc98c8d |
2
.github/workflows/e2e.yml
vendored
2
.github/workflows/e2e.yml
vendored
@ -59,6 +59,6 @@ jobs:
|
||||
TESTFLAGS: -coverprofile=/tmp/coverage/coverage.txt
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: ./build/coverage/coverage.txt
|
||||
|
||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -31,7 +31,7 @@ jobs:
|
||||
targets: test-coverage
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: ./build/coverage/coverage.txt
|
||||
|
||||
@ -73,7 +73,7 @@ jobs:
|
||||
shell: bash
|
||||
-
|
||||
name: Send to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: /tmp/coverage.txt
|
||||
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
|
||||
|
||||
133
cli-plugins/socket/socket_test.go
Normal file
133
cli-plugins/socket/socket_test.go
Normal file
@ -0,0 +1,133 @@
|
||||
package socket
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/poll"
|
||||
)
|
||||
|
||||
func TestSetupConn(t *testing.T) {
|
||||
t.Run("updates conn when connected", func(t *testing.T) {
|
||||
var conn *net.UnixConn
|
||||
listener, err := SetupConn(&conn)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, listener != nil, "returned nil listener but no error")
|
||||
addr, err := net.ResolveUnixAddr("unix", listener.Addr().String())
|
||||
assert.NilError(t, err, "failed to resolve listener address")
|
||||
|
||||
_, err = net.DialUnix("unix", nil, addr)
|
||||
assert.NilError(t, err, "failed to dial returned listener")
|
||||
|
||||
pollConnNotNil(t, &conn)
|
||||
})
|
||||
|
||||
t.Run("allows reconnects", func(t *testing.T) {
|
||||
var conn *net.UnixConn
|
||||
listener, err := SetupConn(&conn)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, listener != nil, "returned nil listener but no error")
|
||||
addr, err := net.ResolveUnixAddr("unix", listener.Addr().String())
|
||||
assert.NilError(t, err, "failed to resolve listener address")
|
||||
|
||||
otherConn, err := net.DialUnix("unix", nil, addr)
|
||||
assert.NilError(t, err, "failed to dial returned listener")
|
||||
|
||||
otherConn.Close()
|
||||
|
||||
_, err = net.DialUnix("unix", nil, addr)
|
||||
assert.NilError(t, err, "failed to redial listener")
|
||||
})
|
||||
|
||||
t.Run("does not leak sockets to local directory", func(t *testing.T) {
|
||||
var conn *net.UnixConn
|
||||
listener, err := SetupConn(&conn)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, listener != nil, "returned nil listener but no error")
|
||||
checkDirNoPluginSocket(t)
|
||||
|
||||
addr, err := net.ResolveUnixAddr("unix", listener.Addr().String())
|
||||
assert.NilError(t, err, "failed to resolve listener address")
|
||||
_, err = net.DialUnix("unix", nil, addr)
|
||||
assert.NilError(t, err, "failed to dial returned listener")
|
||||
checkDirNoPluginSocket(t)
|
||||
})
|
||||
}
|
||||
|
||||
func checkDirNoPluginSocket(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
files, err := os.ReadDir(".")
|
||||
assert.NilError(t, err, "failed to list files in dir to check for leaked sockets")
|
||||
|
||||
for _, f := range files {
|
||||
info, err := f.Info()
|
||||
assert.NilError(t, err, "failed to check file info")
|
||||
// check for a socket with `docker_cli_` in the name (from `SetupConn()`)
|
||||
if strings.Contains(f.Name(), "docker_cli_") && info.Mode().Type() == fs.ModeSocket {
|
||||
t.Fatal("found socket in a local directory")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectAndWait(t *testing.T) {
|
||||
t.Run("calls cancel func on EOF", func(t *testing.T) {
|
||||
var conn *net.UnixConn
|
||||
listener, err := SetupConn(&conn)
|
||||
assert.NilError(t, err, "failed to setup listener")
|
||||
|
||||
done := make(chan struct{})
|
||||
t.Setenv(EnvKey, listener.Addr().String())
|
||||
cancelFunc := func() {
|
||||
done <- struct{}{}
|
||||
}
|
||||
ConnectAndWait(cancelFunc)
|
||||
pollConnNotNil(t, &conn)
|
||||
conn.Close()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
t.Fatal("cancel function not closed after 10ms")
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: this test cannot be executed with `t.Parallel()`, due to
|
||||
// relying on goroutine numbers to ensure correct behaviour
|
||||
t.Run("connect goroutine exits after EOF", func(t *testing.T) {
|
||||
var conn *net.UnixConn
|
||||
listener, err := SetupConn(&conn)
|
||||
assert.NilError(t, err, "failed to setup listener")
|
||||
t.Setenv(EnvKey, listener.Addr().String())
|
||||
numGoroutines := runtime.NumGoroutine()
|
||||
|
||||
ConnectAndWait(func() {})
|
||||
assert.Equal(t, runtime.NumGoroutine(), numGoroutines+1)
|
||||
|
||||
pollConnNotNil(t, &conn)
|
||||
conn.Close()
|
||||
poll.WaitOn(t, func(t poll.LogT) poll.Result {
|
||||
if runtime.NumGoroutine() > numGoroutines+1 {
|
||||
return poll.Continue("waiting for connect goroutine to exit")
|
||||
}
|
||||
return poll.Success()
|
||||
}, poll.WithDelay(1*time.Millisecond), poll.WithTimeout(10*time.Millisecond))
|
||||
})
|
||||
}
|
||||
|
||||
func pollConnNotNil(t *testing.T, conn **net.UnixConn) {
|
||||
t.Helper()
|
||||
|
||||
poll.WaitOn(t, func(t poll.LogT) poll.Result {
|
||||
if *conn == nil {
|
||||
return poll.Continue("waiting for conn to not be nil")
|
||||
}
|
||||
return poll.Success()
|
||||
}, poll.WithDelay(1*time.Millisecond), poll.WithTimeout(10*time.Millisecond))
|
||||
}
|
||||
@ -60,6 +60,7 @@ The sections below provide an overview of available third-party plugins.
|
||||
| [Infinit volume plugin](https://infinit.sh/documentation/docker/volume-plugin) | A volume plugin that makes it easy to mount and manage Infinit volumes using Docker. |
|
||||
| [IPFS Volume Plugin](https://github.com/vdemeester/docker-volume-ipfs) | An open source volume plugin that allows using an [ipfs](https://ipfs.io/) filesystem as a volume. |
|
||||
| [Keywhiz plugin](https://github.com/calavera/docker-volume-keywhiz) | A plugin that provides credentials and secret management using Keywhiz as a central repository. |
|
||||
| [Linode Volume Plugin](https://github.com/linode/docker-volume-linode) | A plugin that adds the ability to manage Linode Block Storage as Docker Volumes from within a Linode. |
|
||||
| [Local Persist Plugin](https://github.com/CWSpear/local-persist) | A volume plugin that extends the default `local` driver's functionality by allowing you specify a mountpoint anywhere on the host, which enables the files to *always persist*, even if the volume is removed via `docker volume rm`. |
|
||||
| [NetApp Plugin](https://github.com/NetApp/netappdvp) (nDVP) | A volume plugin that provides direct integration with the Docker ecosystem for the NetApp storage portfolio. The nDVP package supports the provisioning and management of storage resources from the storage platform to Docker hosts, with a robust framework for adding additional platforms in the future. |
|
||||
| [Netshare plugin](https://github.com/ContainX/docker-volume-netshare) | A volume plugin that provides volume management for NFS 3/4, AWS EFS and CIFS file systems. |
|
||||
|
||||
123
e2e/cli-plugins/plugins/presocket/main.go
Normal file
123
e2e/cli-plugins/plugins/presocket/main.go
Normal file
@ -0,0 +1,123 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/plugin"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.Run(RootCmd, manager.Metadata{
|
||||
SchemaVersion: "0.1.0",
|
||||
Vendor: "Docker Inc.",
|
||||
Version: "test",
|
||||
})
|
||||
}
|
||||
|
||||
func RootCmd(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "presocket",
|
||||
Short: "testing plugin that does not connect to the socket",
|
||||
// override PersistentPreRunE so that the plugin default
|
||||
// PersistentPreRunE doesn't run, simulating a plugin built
|
||||
// with a pre-socket-communication version of the CLI
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.AddCommand(&cobra.Command{
|
||||
Use: "test-no-socket",
|
||||
Short: "test command that runs until it receives a SIGINT",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
go func() {
|
||||
<-cmd.Context().Done()
|
||||
fmt.Fprintln(dockerCli.Out(), "context cancelled")
|
||||
os.Exit(2)
|
||||
}()
|
||||
signalCh := make(chan os.Signal, 10)
|
||||
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
for range signalCh {
|
||||
fmt.Fprintln(dockerCli.Out(), "received SIGINT")
|
||||
}
|
||||
}()
|
||||
<-time.After(3 * time.Second)
|
||||
fmt.Fprintln(dockerCli.Err(), "exit after 3 seconds")
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
cmd.AddCommand(&cobra.Command{
|
||||
Use: "test-socket",
|
||||
Short: "test command that runs until it receives a SIGINT",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return plugin.PersistentPreRunE(cmd, args)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
go func() {
|
||||
<-cmd.Context().Done()
|
||||
fmt.Fprintln(dockerCli.Out(), "context cancelled")
|
||||
os.Exit(2)
|
||||
}()
|
||||
signalCh := make(chan os.Signal, 10)
|
||||
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
for range signalCh {
|
||||
fmt.Fprintln(dockerCli.Out(), "received SIGINT")
|
||||
}
|
||||
}()
|
||||
<-time.After(3 * time.Second)
|
||||
fmt.Fprintln(dockerCli.Err(), "exit after 3 seconds")
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
cmd.AddCommand(&cobra.Command{
|
||||
Use: "test-socket-ignore-context",
|
||||
Short: "test command that runs until it receives a SIGINT",
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return plugin.PersistentPreRunE(cmd, args)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
signalCh := make(chan os.Signal, 10)
|
||||
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
for range signalCh {
|
||||
fmt.Fprintln(dockerCli.Out(), "received SIGINT")
|
||||
}
|
||||
}()
|
||||
<-time.After(3 * time.Second)
|
||||
fmt.Fprintln(dockerCli.Err(), "exit after 3 seconds")
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
cmd.AddCommand(&cobra.Command{
|
||||
Use: "tty",
|
||||
Short: "test command that attempts to read from the TTY",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
b := make([]byte, 1)
|
||||
_, _ = dockerCli.In().Read(b)
|
||||
done <- struct{}{}
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(2 * time.Second):
|
||||
fmt.Fprint(dockerCli.Err(), "timeout after 2 seconds")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return &cmd
|
||||
}
|
||||
235
e2e/cli-plugins/socket_test.go
Normal file
235
e2e/cli-plugins/socket_test.go
Normal file
@ -0,0 +1,235 @@
|
||||
package cliplugins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/creack/pty"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
// TestPluginSocketBackwardsCompatible executes a plugin binary
|
||||
// that does not connect to the CLI plugin socket, simulating
|
||||
// a plugin compiled against an older version of the CLI, and
|
||||
// ensures that backwards compatibility is maintained.
|
||||
func TestPluginSocketBackwardsCompatible(t *testing.T) {
|
||||
run, _, cleanup := prepare(t)
|
||||
defer cleanup()
|
||||
|
||||
t.Run("attached", func(t *testing.T) {
|
||||
t.Run("the plugin gets signalled if attached to a TTY", func(t *testing.T) {
|
||||
cmd := run("presocket", "test-no-socket")
|
||||
command := exec.Command(cmd.Command[0], cmd.Command[1:]...)
|
||||
|
||||
ptmx, err := pty.Start(command)
|
||||
assert.NilError(t, err, "failed to launch command with fake TTY")
|
||||
|
||||
// send a SIGINT to the process group after 1 second, since
|
||||
// we're simulating an "attached TTY" scenario and a TTY would
|
||||
// send a signal to the process group
|
||||
go func() {
|
||||
<-time.After(time.Second)
|
||||
err := syscall.Kill(-command.Process.Pid, syscall.SIGINT)
|
||||
assert.NilError(t, err, "failed to signal process group")
|
||||
}()
|
||||
bytes, err := io.ReadAll(ptmx)
|
||||
if err != nil && !strings.Contains(err.Error(), "input/output error") {
|
||||
t.Fatal("failed to get command output")
|
||||
}
|
||||
|
||||
// the plugin is attached to the TTY, so the parent process
|
||||
// ignores the received signal, and the plugin receives a SIGINT
|
||||
// as well
|
||||
assert.Equal(t, string(bytes), "received SIGINT\r\nexit after 3 seconds\r\n")
|
||||
})
|
||||
|
||||
// ensure that we don't break plugins that attempt to read from the TTY
|
||||
// (see: https://github.com/moby/moby/issues/47073)
|
||||
// (remove me if/when we decide to break compatibility here)
|
||||
t.Run("the plugin can read from the TTY", func(t *testing.T) {
|
||||
cmd := run("presocket", "tty")
|
||||
command := exec.Command(cmd.Command[0], cmd.Command[1:]...)
|
||||
|
||||
ptmx, err := pty.Start(command)
|
||||
assert.NilError(t, err, "failed to launch command with fake TTY")
|
||||
_, _ = ptmx.Write([]byte("hello!"))
|
||||
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
<-time.After(time.Second)
|
||||
_, err := io.ReadAll(ptmx)
|
||||
done <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case cmdErr := <-done:
|
||||
if cmdErr != nil && !strings.Contains(cmdErr.Error(), "input/output error") {
|
||||
t.Fatal("failed to get command output")
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("timed out! plugin process probably stuck")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("detached", func(t *testing.T) {
|
||||
t.Run("the plugin does not get signalled", func(t *testing.T) {
|
||||
cmd := run("presocket", "test-no-socket")
|
||||
command := exec.Command(cmd.Command[0], cmd.Command[1:]...)
|
||||
t.Log(strings.Join(command.Args, " "))
|
||||
command.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-time.After(time.Second)
|
||||
// we're signalling the parent process directly and not
|
||||
// the process group, since we're testing the case where
|
||||
// the process is detached and not simulating a CTRL-C
|
||||
// from a TTY
|
||||
err := syscall.Kill(command.Process.Pid, syscall.SIGINT)
|
||||
assert.NilError(t, err, "failed to signal process group")
|
||||
}()
|
||||
bytes, err := command.CombinedOutput()
|
||||
t.Log("command output: " + string(bytes))
|
||||
assert.NilError(t, err, "failed to run command")
|
||||
|
||||
// the plugin process does not receive a SIGINT
|
||||
// so it exits after 3 seconds and prints this message
|
||||
assert.Equal(t, string(bytes), "exit after 3 seconds\n")
|
||||
})
|
||||
|
||||
t.Run("the main CLI exits after 3 signals", func(t *testing.T) {
|
||||
cmd := run("presocket", "test-no-socket")
|
||||
command := exec.Command(cmd.Command[0], cmd.Command[1:]...)
|
||||
t.Log(strings.Join(command.Args, " "))
|
||||
command.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-time.After(time.Second)
|
||||
// we're signalling the parent process directly and not
|
||||
// the process group, since we're testing the case where
|
||||
// the process is detached and not simulating a CTRL-C
|
||||
// from a TTY
|
||||
err := syscall.Kill(command.Process.Pid, syscall.SIGINT)
|
||||
assert.NilError(t, err, "failed to signal process group")
|
||||
// TODO: look into CLI signal handling, it's currently necessary
|
||||
// to add a short delay between each signal in order for the CLI
|
||||
// process to consistently pick them all up.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
err = syscall.Kill(command.Process.Pid, syscall.SIGINT)
|
||||
assert.NilError(t, err, "failed to signal process group")
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
err = syscall.Kill(command.Process.Pid, syscall.SIGINT)
|
||||
assert.NilError(t, err, "failed to signal process group")
|
||||
}()
|
||||
bytes, err := command.CombinedOutput()
|
||||
assert.ErrorContains(t, err, "exit status 1")
|
||||
|
||||
// the plugin process does not receive a SIGINT and does
|
||||
// the CLI cannot cancel it over the socket, so it kills
|
||||
// the plugin process and forcefully exits
|
||||
assert.Equal(t, string(bytes), "got 3 SIGTERM/SIGINTs, forcefully exiting\n")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestPluginSocketCommunication(t *testing.T) {
|
||||
run, _, cleanup := prepare(t)
|
||||
defer cleanup()
|
||||
|
||||
t.Run("attached", func(t *testing.T) {
|
||||
t.Run("the socket is not closed + the plugin receives a signal due to pgid", func(t *testing.T) {
|
||||
cmd := run("presocket", "test-socket")
|
||||
command := exec.Command(cmd.Command[0], cmd.Command[1:]...)
|
||||
|
||||
ptmx, err := pty.Start(command)
|
||||
assert.NilError(t, err, "failed to launch command with fake TTY")
|
||||
|
||||
// send a SIGINT to the process group after 1 second, since
|
||||
// we're simulating an "attached TTY" scenario and a TTY would
|
||||
// send a signal to the process group
|
||||
go func() {
|
||||
<-time.After(time.Second)
|
||||
err := syscall.Kill(-command.Process.Pid, syscall.SIGINT)
|
||||
assert.NilError(t, err, "failed to signal process group")
|
||||
}()
|
||||
bytes, err := io.ReadAll(ptmx)
|
||||
if err != nil && !strings.Contains(err.Error(), "input/output error") {
|
||||
t.Fatal("failed to get command output")
|
||||
}
|
||||
|
||||
// the plugin is attached to the TTY, so the parent process
|
||||
// ignores the received signal, and the plugin receives a SIGINT
|
||||
// as well
|
||||
assert.Equal(t, string(bytes), "received SIGINT\r\nexit after 3 seconds\r\n")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("detached", func(t *testing.T) {
|
||||
t.Run("the plugin does not get signalled", func(t *testing.T) {
|
||||
cmd := run("presocket", "test-socket")
|
||||
command := exec.Command(cmd.Command[0], cmd.Command[1:]...)
|
||||
outB := bytes.Buffer{}
|
||||
command.Stdout = &outB
|
||||
command.Stderr = &outB
|
||||
command.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
|
||||
// send a SIGINT to the process group after 1 second
|
||||
go func() {
|
||||
<-time.After(time.Second)
|
||||
err := syscall.Kill(command.Process.Pid, syscall.SIGINT)
|
||||
assert.NilError(t, err, "failed to signal CLI process")
|
||||
}()
|
||||
err := command.Run()
|
||||
t.Log(outB.String())
|
||||
assert.ErrorContains(t, err, "exit status 2")
|
||||
|
||||
// the plugin does not get signalled, but it does get it's
|
||||
// context cancelled by the CLI through the socket
|
||||
assert.Equal(t, outB.String(), "context cancelled\n")
|
||||
})
|
||||
|
||||
t.Run("the main CLI exits after 3 signals", func(t *testing.T) {
|
||||
cmd := run("presocket", "test-socket-ignore-context")
|
||||
command := exec.Command(cmd.Command[0], cmd.Command[1:]...)
|
||||
command.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-time.After(time.Second)
|
||||
// we're signalling the parent process directly and not
|
||||
// the process group, since we're testing the case where
|
||||
// the process is detached and not simulating a CTRL-C
|
||||
// from a TTY
|
||||
err := syscall.Kill(command.Process.Pid, syscall.SIGINT)
|
||||
assert.NilError(t, err, "failed to signal CLI process")
|
||||
// TODO: same as above TODO, CLI signal handling is not consistent
|
||||
// with multiple signals without intervals
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
err = syscall.Kill(command.Process.Pid, syscall.SIGINT)
|
||||
assert.NilError(t, err, "failed to signal CLI process")
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
err = syscall.Kill(command.Process.Pid, syscall.SIGINT)
|
||||
assert.NilError(t, err, "failed to signal CLI process§")
|
||||
}()
|
||||
bytes, err := command.CombinedOutput()
|
||||
assert.ErrorContains(t, err, "exit status 1")
|
||||
|
||||
// the plugin process does not receive a SIGINT and does
|
||||
// not exit after having it's context cancelled, so the CLI
|
||||
// kills the plugin process and forcefully exits
|
||||
assert.Equal(t, string(bytes), "got 3 SIGTERM/SIGINTs, forcefully exiting\n")
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -2,6 +2,8 @@
|
||||
# Run integration tests against the latest docker-ce dind
|
||||
set -eu -o pipefail
|
||||
|
||||
source ./scripts/build/.variables
|
||||
|
||||
container_ip() {
|
||||
local cid=$1
|
||||
local network=$2
|
||||
@ -69,7 +71,7 @@ runtests() {
|
||||
GOPATH="$GOPATH" \
|
||||
PATH="$PWD/build/:/usr/bin:/usr/local/bin:/usr/local/go/bin" \
|
||||
HOME="$HOME" \
|
||||
DOCKER_CLI_E2E_PLUGINS_EXTRA_DIRS="$PWD/build/plugins-linux-amd64" \
|
||||
DOCKER_CLI_E2E_PLUGINS_EXTRA_DIRS="$PWD/build/plugins-linux-${GOARCH}" \
|
||||
GO111MODULE=auto \
|
||||
"$(command -v gotestsum)" -- ${TESTDIRS:-./e2e/...} ${TESTFLAGS-}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ require (
|
||||
github.com/creack/pty v1.1.21
|
||||
github.com/distribution/reference v0.5.0
|
||||
github.com/docker/distribution v2.8.3+incompatible
|
||||
github.com/docker/docker v25.0.0+incompatible
|
||||
github.com/docker/docker v25.0.2+incompatible
|
||||
github.com/docker/docker-credential-helpers v0.8.1
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
|
||||
@ -54,8 +54,8 @@ github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v25.0.0+incompatible h1:g9b6wZTblhMgzOT2tspESstfw6ySZ9kdm94BLDKaZac=
|
||||
github.com/docker/docker v25.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v25.0.2+incompatible h1:/OaKeauroa10K4Nqavw4zlhcDq/WBcPMc5DbjOGgozY=
|
||||
github.com/docker/docker v25.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo=
|
||||
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||
|
||||
2
vendor/github.com/docker/docker/builder/remotecontext/urlutil/urlutil.go
generated
vendored
2
vendor/github.com/docker/docker/builder/remotecontext/urlutil/urlutil.go
generated
vendored
@ -12,7 +12,7 @@ import (
|
||||
|
||||
// urlPathWithFragmentSuffix matches fragments to use as Git reference and build
|
||||
// context from the Git repository. See IsGitURL for details.
|
||||
var urlPathWithFragmentSuffix = regexp.MustCompile(".git(?:#.+)?$")
|
||||
var urlPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`)
|
||||
|
||||
// IsURL returns true if the provided str is an HTTP(S) URL by checking if it
|
||||
// has a http:// or https:// scheme. No validation is performed to verify if the
|
||||
|
||||
18
vendor/github.com/docker/docker/pkg/system/xattrs.go
generated
vendored
Normal file
18
vendor/github.com/docker/docker/pkg/system/xattrs.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
type XattrError struct {
|
||||
Op string
|
||||
Attr string
|
||||
Path string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *XattrError) Error() string { return e.Op + " " + e.Attr + " " + e.Path + ": " + e.Err.Error() }
|
||||
|
||||
func (e *XattrError) Unwrap() error { return e.Err }
|
||||
|
||||
// Timeout reports whether this error represents a timeout.
|
||||
func (e *XattrError) Timeout() bool {
|
||||
t, ok := e.Err.(interface{ Timeout() bool })
|
||||
return ok && t.Timeout()
|
||||
}
|
||||
12
vendor/github.com/docker/docker/pkg/system/xattrs_linux.go
generated
vendored
12
vendor/github.com/docker/docker/pkg/system/xattrs_linux.go
generated
vendored
@ -1,8 +1,6 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
@ -10,8 +8,8 @@ import (
|
||||
// and associated with the given path in the file system.
|
||||
// It will returns a nil slice and nil error if the xattr is not set.
|
||||
func Lgetxattr(path string, attr string) ([]byte, error) {
|
||||
pathErr := func(err error) ([]byte, error) {
|
||||
return nil, &fs.PathError{Op: "lgetxattr", Path: path, Err: err}
|
||||
sysErr := func(err error) ([]byte, error) {
|
||||
return nil, &XattrError{Op: "lgetxattr", Attr: attr, Path: path, Err: err}
|
||||
}
|
||||
|
||||
// Start with a 128 length byte array
|
||||
@ -22,7 +20,7 @@ func Lgetxattr(path string, attr string) ([]byte, error) {
|
||||
// Buffer too small, use zero-sized buffer to get the actual size
|
||||
sz, errno = unix.Lgetxattr(path, attr, []byte{})
|
||||
if errno != nil {
|
||||
return pathErr(errno)
|
||||
return sysErr(errno)
|
||||
}
|
||||
dest = make([]byte, sz)
|
||||
sz, errno = unix.Lgetxattr(path, attr, dest)
|
||||
@ -32,7 +30,7 @@ func Lgetxattr(path string, attr string) ([]byte, error) {
|
||||
case errno == unix.ENODATA:
|
||||
return nil, nil
|
||||
case errno != nil:
|
||||
return pathErr(errno)
|
||||
return sysErr(errno)
|
||||
}
|
||||
|
||||
return dest[:sz], nil
|
||||
@ -43,7 +41,7 @@ func Lgetxattr(path string, attr string) ([]byte, error) {
|
||||
func Lsetxattr(path string, attr string, data []byte, flags int) error {
|
||||
err := unix.Lsetxattr(path, attr, data, flags)
|
||||
if err != nil {
|
||||
return &fs.PathError{Op: "lsetxattr", Path: path, Err: err}
|
||||
return &XattrError{Op: "lsetxattr", Attr: attr, Path: path, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -53,7 +53,7 @@ github.com/docker/distribution/registry/client/transport
|
||||
github.com/docker/distribution/registry/storage/cache
|
||||
github.com/docker/distribution/registry/storage/cache/memory
|
||||
github.com/docker/distribution/uuid
|
||||
# github.com/docker/docker v25.0.0+incompatible
|
||||
# github.com/docker/docker v25.0.2+incompatible
|
||||
## explicit
|
||||
github.com/docker/docker/api
|
||||
github.com/docker/docker/api/types
|
||||
|
||||
Reference in New Issue
Block a user