Browse code

client: ContainerStop(), ContainerRestart(): support stop-signal

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2022/02/16 19:36:37
Showing 12 changed files
... ...
@@ -3,18 +3,22 @@ package client // import "github.com/docker/docker/client"
3 3
 import (
4 4
 	"context"
5 5
 	"net/url"
6
-	"time"
6
+	"strconv"
7 7
 
8
-	timetypes "github.com/docker/docker/api/types/time"
8
+	"github.com/docker/docker/api/types/container"
9
+	"github.com/docker/docker/api/types/versions"
9 10
 )
10 11
 
11 12
 // ContainerRestart stops and starts a container again.
12 13
 // It makes the daemon wait for the container to be up again for
13 14
 // a specific amount of time, given the timeout.
14
-func (cli *Client) ContainerRestart(ctx context.Context, containerID string, timeout *time.Duration) error {
15
+func (cli *Client) ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error {
15 16
 	query := url.Values{}
16
-	if timeout != nil {
17
-		query.Set("t", timetypes.DurationToSecondsString(*timeout))
17
+	if options.Timeout != nil {
18
+		query.Set("t", strconv.Itoa(*options.Timeout))
19
+	}
20
+	if options.Signal != "" && versions.GreaterThanOrEqualTo(cli.version, "1.42") {
21
+		query.Set("signal", options.Signal)
18 22
 	}
19 23
 	resp, err := cli.post(ctx, "/containers/"+containerID+"/restart", query, nil, nil)
20 24
 	ensureReaderClosed(resp)
... ...
@@ -8,8 +8,8 @@ import (
8 8
 	"net/http"
9 9
 	"strings"
10 10
 	"testing"
11
-	"time"
12 11
 
12
+	"github.com/docker/docker/api/types/container"
13 13
 	"github.com/docker/docker/errdefs"
14 14
 )
15 15
 
... ...
@@ -17,20 +17,23 @@ func TestContainerRestartError(t *testing.T) {
17 17
 	client := &Client{
18 18
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
19 19
 	}
20
-	timeout := 0 * time.Second
21
-	err := client.ContainerRestart(context.Background(), "nothing", &timeout)
20
+	err := client.ContainerRestart(context.Background(), "nothing", container.StopOptions{})
22 21
 	if !errdefs.IsSystem(err) {
23 22
 		t.Fatalf("expected a Server Error, got %[1]T: %[1]v", err)
24 23
 	}
25 24
 }
26 25
 
27 26
 func TestContainerRestart(t *testing.T) {
28
-	expectedURL := "/containers/container_id/restart"
27
+	const expectedURL = "/v1.42/containers/container_id/restart"
29 28
 	client := &Client{
30 29
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
31 30
 			if !strings.HasPrefix(req.URL.Path, expectedURL) {
32 31
 				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
33 32
 			}
33
+			s := req.URL.Query().Get("signal")
34
+			if s != "SIGKILL" {
35
+				return nil, fmt.Errorf("signal not set in URL query. Expected 'SIGKILL', got '%s'", s)
36
+			}
34 37
 			t := req.URL.Query().Get("t")
35 38
 			if t != "100" {
36 39
 				return nil, fmt.Errorf("t (timeout) not set in URL query properly. Expected '100', got %s", t)
... ...
@@ -40,9 +43,13 @@ func TestContainerRestart(t *testing.T) {
40 40
 				Body:       io.NopCloser(bytes.NewReader([]byte(""))),
41 41
 			}, nil
42 42
 		}),
43
+		version: "1.42",
43 44
 	}
44
-	timeout := 100 * time.Second
45
-	err := client.ContainerRestart(context.Background(), "container_id", &timeout)
45
+	timeout := 100
46
+	err := client.ContainerRestart(context.Background(), "container_id", container.StopOptions{
47
+		Signal:  "SIGKILL",
48
+		Timeout: &timeout,
49
+	})
46 50
 	if err != nil {
47 51
 		t.Fatal(err)
48 52
 	}
... ...
@@ -3,9 +3,10 @@ package client // import "github.com/docker/docker/client"
3 3
 import (
4 4
 	"context"
5 5
 	"net/url"
6
-	"time"
6
+	"strconv"
7 7
 
8
-	timetypes "github.com/docker/docker/api/types/time"
8
+	"github.com/docker/docker/api/types/container"
9
+	"github.com/docker/docker/api/types/versions"
9 10
 )
10 11
 
11 12
 // ContainerStop stops a container. In case the container fails to stop
... ...
@@ -15,10 +16,13 @@ import (
15 15
 // If the timeout is nil, the container's StopTimeout value is used, if set,
16 16
 // otherwise the engine default. A negative timeout value can be specified,
17 17
 // meaning no timeout, i.e. no forceful termination is performed.
18
-func (cli *Client) ContainerStop(ctx context.Context, containerID string, timeout *time.Duration) error {
18
+func (cli *Client) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error {
19 19
 	query := url.Values{}
20
-	if timeout != nil {
21
-		query.Set("t", timetypes.DurationToSecondsString(*timeout))
20
+	if options.Timeout != nil {
21
+		query.Set("t", strconv.Itoa(*options.Timeout))
22
+	}
23
+	if options.Signal != "" && versions.GreaterThanOrEqualTo(cli.version, "1.42") {
24
+		query.Set("signal", options.Signal)
22 25
 	}
23 26
 	resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil)
24 27
 	ensureReaderClosed(resp)
... ...
@@ -8,8 +8,8 @@ import (
8 8
 	"net/http"
9 9
 	"strings"
10 10
 	"testing"
11
-	"time"
12 11
 
12
+	"github.com/docker/docker/api/types/container"
13 13
 	"github.com/docker/docker/errdefs"
14 14
 )
15 15
 
... ...
@@ -17,20 +17,23 @@ func TestContainerStopError(t *testing.T) {
17 17
 	client := &Client{
18 18
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
19 19
 	}
20
-	timeout := 0 * time.Second
21
-	err := client.ContainerStop(context.Background(), "nothing", &timeout)
20
+	err := client.ContainerStop(context.Background(), "nothing", container.StopOptions{})
22 21
 	if !errdefs.IsSystem(err) {
23 22
 		t.Fatalf("expected a Server Error, got %[1]T: %[1]v", err)
24 23
 	}
25 24
 }
26 25
 
27 26
 func TestContainerStop(t *testing.T) {
28
-	expectedURL := "/containers/container_id/stop"
27
+	const expectedURL = "/v1.42/containers/container_id/stop"
29 28
 	client := &Client{
30 29
 		client: newMockClient(func(req *http.Request) (*http.Response, error) {
31 30
 			if !strings.HasPrefix(req.URL.Path, expectedURL) {
32 31
 				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
33 32
 			}
33
+			s := req.URL.Query().Get("signal")
34
+			if s != "SIGKILL" {
35
+				return nil, fmt.Errorf("signal not set in URL query. Expected 'SIGKILL', got '%s'", s)
36
+			}
34 37
 			t := req.URL.Query().Get("t")
35 38
 			if t != "100" {
36 39
 				return nil, fmt.Errorf("t (timeout) not set in URL query properly. Expected '100', got %s", t)
... ...
@@ -40,9 +43,13 @@ func TestContainerStop(t *testing.T) {
40 40
 				Body:       io.NopCloser(bytes.NewReader([]byte(""))),
41 41
 			}, nil
42 42
 		}),
43
+		version: "1.42",
43 44
 	}
44
-	timeout := 100 * time.Second
45
-	err := client.ContainerStop(context.Background(), "container_id", &timeout)
45
+	timeout := 100
46
+	err := client.ContainerStop(context.Background(), "container_id", container.StopOptions{
47
+		Signal:  "SIGKILL",
48
+		Timeout: &timeout,
49
+	})
46 50
 	if err != nil {
47 51
 		t.Fatal(err)
48 52
 	}
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"io"
6 6
 	"net"
7 7
 	"net/http"
8
-	"time"
9 8
 
10 9
 	"github.com/docker/docker/api/types"
11 10
 	"github.com/docker/docker/api/types/container"
... ...
@@ -65,12 +64,12 @@ type ContainerAPIClient interface {
65 65
 	ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error
66 66
 	ContainerRename(ctx context.Context, container, newContainerName string) error
67 67
 	ContainerResize(ctx context.Context, container string, options types.ResizeOptions) error
68
-	ContainerRestart(ctx context.Context, container string, timeout *time.Duration) error
68
+	ContainerRestart(ctx context.Context, container string, options container.StopOptions) error
69 69
 	ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error)
70 70
 	ContainerStats(ctx context.Context, container string, stream bool) (types.ContainerStats, error)
71 71
 	ContainerStatsOneShot(ctx context.Context, container string) (types.ContainerStats, error)
72 72
 	ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error
73
-	ContainerStop(ctx context.Context, container string, timeout *time.Duration) error
73
+	ContainerStop(ctx context.Context, container string, options container.StopOptions) error
74 74
 	ContainerTop(ctx context.Context, container string, arguments []string) (container.ContainerTopOKBody, error)
75 75
 	ContainerUnpause(ctx context.Context, container string) error
76 76
 	ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.ContainerUpdateOKBody, error)
... ...
@@ -913,8 +913,8 @@ func (s *DockerSuite) TestContainerAPIRestart(c *testing.T) {
913 913
 	assert.NilError(c, err)
914 914
 	defer cli.Close()
915 915
 
916
-	timeout := 1 * time.Second
917
-	err = cli.ContainerRestart(context.Background(), name, &timeout)
916
+	timeout := 1
917
+	err = cli.ContainerRestart(context.Background(), name, container.StopOptions{Timeout: &timeout})
918 918
 	assert.NilError(c, err)
919 919
 
920 920
 	assert.Assert(c, waitInspect(name, "{{ .State.Restarting  }} {{ .State.Running  }}", "false true", 15*time.Second) == nil)
... ...
@@ -930,7 +930,7 @@ func (s *DockerSuite) TestContainerAPIRestartNotimeoutParam(c *testing.T) {
930 930
 	assert.NilError(c, err)
931 931
 	defer cli.Close()
932 932
 
933
-	err = cli.ContainerRestart(context.Background(), name, nil)
933
+	err = cli.ContainerRestart(context.Background(), name, container.StopOptions{})
934 934
 	assert.NilError(c, err)
935 935
 
936 936
 	assert.Assert(c, waitInspect(name, "{{ .State.Restarting  }} {{ .State.Running  }}", "false true", 15*time.Second) == nil)
... ...
@@ -965,19 +965,23 @@ func (s *DockerSuite) TestContainerAPIStart(c *testing.T) {
965 965
 func (s *DockerSuite) TestContainerAPIStop(c *testing.T) {
966 966
 	name := "test-api-stop"
967 967
 	runSleepingContainer(c, "-i", "--name", name)
968
-	timeout := 30 * time.Second
968
+	timeout := 30
969 969
 
970 970
 	cli, err := client.NewClientWithOpts(client.FromEnv)
971 971
 	assert.NilError(c, err)
972 972
 	defer cli.Close()
973 973
 
974
-	err = cli.ContainerStop(context.Background(), name, &timeout)
974
+	err = cli.ContainerStop(context.Background(), name, container.StopOptions{
975
+		Timeout: &timeout,
976
+	})
975 977
 	assert.NilError(c, err)
976 978
 	assert.Assert(c, waitInspect(name, "{{ .State.Running  }}", "false", 60*time.Second) == nil)
977 979
 
978 980
 	// second call to start should give 304
979 981
 	// maybe add ContainerStartWithRaw to test it
980
-	err = cli.ContainerStop(context.Background(), name, &timeout)
982
+	err = cli.ContainerStop(context.Background(), name, container.StopOptions{
983
+		Timeout: &timeout,
984
+	})
981 985
 	assert.NilError(c, err)
982 986
 }
983 987
 
... ...
@@ -1255,7 +1259,7 @@ func (s *DockerSuite) TestContainerAPIPostContainerStop(c *testing.T) {
1255 1255
 	assert.NilError(c, err)
1256 1256
 	defer cli.Close()
1257 1257
 
1258
-	err = cli.ContainerStop(context.Background(), containerID, nil)
1258
+	err = cli.ContainerStop(context.Background(), containerID, container.StopOptions{})
1259 1259
 	assert.NilError(c, err)
1260 1260
 	assert.Assert(c, waitInspect(containerID, "{{ .State.Running  }}", "false", 60*time.Second) == nil)
1261 1261
 }
... ...
@@ -8,6 +8,7 @@ import (
8 8
 
9 9
 	containerderrdefs "github.com/containerd/containerd/errdefs"
10 10
 	"github.com/docker/docker/api/types"
11
+	containertypes "github.com/docker/docker/api/types/container"
11 12
 	"github.com/docker/docker/api/types/events"
12 13
 	"github.com/docker/docker/api/types/filters"
13 14
 	"github.com/docker/docker/api/types/versions"
... ...
@@ -80,7 +81,7 @@ func TestPauseStopPausedContainer(t *testing.T) {
80 80
 	err := client.ContainerPause(ctx, cID)
81 81
 	assert.NilError(t, err)
82 82
 
83
-	err = client.ContainerStop(ctx, cID, nil)
83
+	err = client.ContainerStop(ctx, cID, containertypes.StopOptions{})
84 84
 	assert.NilError(t, err)
85 85
 
86 86
 	poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond))
... ...
@@ -143,7 +143,7 @@ func TestRenameAnonymousContainer(t *testing.T) {
143 143
 	assert.NilError(t, err)
144 144
 	// Stop/Start the container to get registered
145 145
 	// FIXME(vdemeester) this is a really weird behavior as it fails otherwise
146
-	err = client.ContainerStop(ctx, container1Name, nil)
146
+	err = client.ContainerStop(ctx, container1Name, containertypes.StopOptions{})
147 147
 	assert.NilError(t, err)
148 148
 	err = client.ContainerStart(ctx, container1Name, types.ContainerStartOptions{})
149 149
 	assert.NilError(t, err)
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"time"
10 10
 
11 11
 	"github.com/docker/docker/api/types"
12
+	containertypes "github.com/docker/docker/api/types/container"
12 13
 	"github.com/docker/docker/integration/internal/container"
13 14
 	"gotest.tools/v3/assert"
14 15
 	"gotest.tools/v3/icmd"
... ...
@@ -56,8 +57,7 @@ func TestStopContainerWithTimeout(t *testing.T) {
56 56
 			t.Parallel()
57 57
 			id := container.Run(ctx, t, client, testCmd)
58 58
 
59
-			timeout := time.Duration(d.timeout) * time.Second
60
-			err := client.ContainerStop(ctx, id, &timeout)
59
+			err := client.ContainerStop(ctx, id, containertypes.StopOptions{Timeout: &d.timeout})
61 60
 			assert.NilError(t, err)
62 61
 
63 62
 			poll.WaitOn(t, container.IsStopped(ctx, client, id),
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"testing"
6 6
 	"time"
7 7
 
8
+	containertypes "github.com/docker/docker/api/types/container"
8 9
 	"github.com/docker/docker/integration/internal/container"
9 10
 	"gotest.tools/v3/assert"
10 11
 	"gotest.tools/v3/poll"
... ...
@@ -29,7 +30,7 @@ func TestStopContainerWithRestartPolicyAlways(t *testing.T) {
29 29
 	}
30 30
 
31 31
 	for _, name := range names {
32
-		err := client.ContainerStop(ctx, name, nil)
32
+		err := client.ContainerStop(ctx, name, containertypes.StopOptions{})
33 33
 		assert.NilError(t, err)
34 34
 	}
35 35
 
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"testing"
7 7
 	"time"
8 8
 
9
+	containertypes "github.com/docker/docker/api/types/container"
9 10
 	"github.com/docker/docker/integration/internal/container"
10 11
 	"gotest.tools/v3/assert"
11 12
 	"gotest.tools/v3/poll"
... ...
@@ -53,8 +54,7 @@ func TestStopContainerWithTimeout(t *testing.T) {
53 53
 			t.Parallel()
54 54
 			id := container.Run(ctx, t, client, testCmd)
55 55
 
56
-			timeout := time.Duration(d.timeout) * time.Second
57
-			err := client.ContainerStop(ctx, id, &timeout)
56
+			err := client.ContainerStop(ctx, id, containertypes.StopOptions{Timeout: &d.timeout})
58 57
 			assert.NilError(t, err)
59 58
 
60 59
 			poll.WaitOn(t, container.IsStopped(ctx, client, id),
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"testing"
6 6
 	"time"
7 7
 
8
+	containertypes "github.com/docker/docker/api/types/container"
8 9
 	"github.com/docker/docker/integration/internal/container"
9 10
 	"github.com/docker/docker/testutil/request"
10 11
 	"gotest.tools/v3/assert"
... ...
@@ -86,7 +87,7 @@ func TestWaitBlocked(t *testing.T) {
86 86
 
87 87
 			waitResC, errC := cli.ContainerWait(ctx, containerID, "")
88 88
 
89
-			err := cli.ContainerStop(ctx, containerID, nil)
89
+			err := cli.ContainerStop(ctx, containerID, containertypes.StopOptions{})
90 90
 			assert.NilError(t, err)
91 91
 
92 92
 			select {