Skip to content

Commit 3f3f6a9

Browse files
authored
Merge pull request #83 from coderbirju/updateContainerStop
feat: add signal option to containerStop
2 parents 0f9194a + c656bbe commit 3f3f6a9

File tree

10 files changed

+72
-27
lines changed

10 files changed

+72
-27
lines changed

api/handlers/container/container.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"context"
99
"io"
1010
"net/http"
11-
"time"
1211

1312
ncTypes "github.com/containerd/nerdctl/v2/pkg/api/types"
1413
"github.com/containerd/nerdctl/v2/pkg/config"
@@ -23,7 +22,7 @@ type Service interface {
2322
Remove(ctx context.Context, cid string, force, removeVolumes bool) error
2423
Wait(ctx context.Context, cid string, options ncTypes.ContainerWaitOptions) error
2524
Start(ctx context.Context, cid string, options ncTypes.ContainerStartOptions) error
26-
Stop(ctx context.Context, cid string, timeout *time.Duration) error
25+
Stop(ctx context.Context, cid string, option ncTypes.ContainerStopOptions) error
2726
Restart(ctx context.Context, cid string, options ncTypes.ContainerRestartOptions) error
2827
Create(ctx context.Context, image string, cmd []string, createOpt ncTypes.ContainerCreateOptions, netOpt ncTypes.NetworkOptions) (string, error)
2928
Inspect(ctx context.Context, cid string, size bool) (*types.Container, error)

api/handlers/container/stop.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ package container
55

66
import (
77
"net/http"
8+
"os"
89
"strconv"
910
"time"
1011

1112
"github.com/containerd/containerd/v2/pkg/namespaces"
13+
ncTypes "github.com/containerd/nerdctl/v2/pkg/api/types"
1214
"github.com/gorilla/mux"
1315
"github.com/sirupsen/logrus"
1416

@@ -24,8 +26,30 @@ func (h *handler) stop(w http.ResponseWriter, r *http.Request) {
2426
}
2527
timeout := time.Second * time.Duration(t)
2628

29+
signal := getSignal(r)
30+
if signal == "" {
31+
signal = "SIGTERM" // Docker/nerdctl default
32+
logrus.Infof("Setting default %s to stop container", signal)
33+
}
34+
35+
// discard unwanted logs by writing to /dev/null
36+
devNull, err := os.OpenFile("/dev/null", os.O_WRONLY, 0600)
37+
if err != nil {
38+
response.JSON(w, http.StatusBadRequest, response.NewErrorFromMsg("failed to open /dev/null"))
39+
return
40+
}
41+
defer devNull.Close()
42+
2743
ctx := namespaces.WithNamespace(r.Context(), h.Config.Namespace)
28-
err = h.service.Stop(ctx, cid, &timeout)
44+
globalOpt := ncTypes.GlobalCommandOptions(*h.Config)
45+
stopOpts := ncTypes.ContainerStopOptions{
46+
Stdout: devNull,
47+
Stderr: devNull,
48+
Timeout: &timeout,
49+
Signal: signal,
50+
GOptions: globalOpt,
51+
}
52+
err = h.service.Stop(ctx, cid, stopOpts)
2953
// map the error into http status code and send response.
3054
if err != nil {
3155
var code int
@@ -44,3 +68,12 @@ func (h *handler) stop(w http.ResponseWriter, r *http.Request) {
4468
// successfully stopped. Send no content status.
4569
response.Status(w, http.StatusNoContent)
4670
}
71+
72+
func getSignal(r *http.Request) string {
73+
signal := r.URL.Query().Get("signal")
74+
if signal == "" {
75+
// If "signal" is not present, check for "s"
76+
signal = r.URL.Query().Get("s")
77+
}
78+
return signal
79+
}

e2e/tests/container_restart.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ func ContainerRestart(opt *option.Option) {
5252
Expect(err).Should(BeNil())
5353
Expect(res.StatusCode).Should(Equal(http.StatusNoContent))
5454

55+
// Add sleep to ensure container has time to restart and execute the date command
56+
time.Sleep(2 * time.Second)
57+
5558
logsRelativeUrl := fmt.Sprintf("/containers/%s/logs", testContainerName)
5659
opts := "?stdout=1" +
5760
"&stderr=0" +

internal/backend/container.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"io"
1212
"os"
1313
"os/exec"
14-
"time"
1514

1615
containerd "github.com/containerd/containerd/v2/client"
1716
"github.com/containerd/nerdctl/v2/pkg/api/types"
@@ -28,7 +27,7 @@ import (
2827
type NerdctlContainerSvc interface {
2928
RemoveContainer(ctx context.Context, c containerd.Container, force bool, removeAnonVolumes bool) error
3029
StartContainer(ctx context.Context, cid string, options types.ContainerStartOptions) error
31-
StopContainer(ctx context.Context, container containerd.Container, timeout *time.Duration) error
30+
StopContainer(ctx context.Context, cid string, options types.ContainerStopOptions) error
3231
CreateContainer(ctx context.Context, args []string, netManager containerutil.NetworkOptionsManager, options types.ContainerCreateOptions) (containerd.Container, func(), error)
3332
InspectContainer(ctx context.Context, c containerd.Container, size bool) (*dockercompat.Container, error)
3433
InspectNetNS(ctx context.Context, pid int) (*native.NetNS, error)
@@ -59,8 +58,8 @@ func (w *NerdctlWrapper) StartContainer(ctx context.Context, cid string, options
5958
}
6059

6160
// StopContainer wrapper function to call nerdctl function to stop a container.
62-
func (*NerdctlWrapper) StopContainer(ctx context.Context, container containerd.Container, timeout *time.Duration) error {
63-
return containerutil.Stop(ctx, container, timeout, "")
61+
func (w *NerdctlWrapper) StopContainer(ctx context.Context, cid string, options types.ContainerStopOptions) error {
62+
return container.Stop(ctx, w.clientWrapper.client, []string{cid}, options)
6463
}
6564

6665
func (w *NerdctlWrapper) CreateContainer(ctx context.Context, args []string, netManager containerutil.NetworkOptionsManager, options types.ContainerCreateOptions) (containerd.Container, func(), error) {

internal/service/container/restart.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package container
55

66
import (
77
"context"
8+
"io"
89

910
"github.com/containerd/nerdctl/v2/pkg/api/types"
1011
"github.com/runfinch/finch-daemon/pkg/errdefs"
@@ -16,10 +17,18 @@ func (s *service) Restart(ctx context.Context, cid string, options types.Contain
1617
return err
1718
}
1819

20+
stopOptions := types.ContainerStopOptions{
21+
Stdout: io.Discard,
22+
Stderr: io.Discard,
23+
Timeout: options.Timeout,
24+
Signal: "SIGTERM",
25+
GOptions: options.GOption,
26+
}
27+
1928
// restart the container and if error occurs then return error otherwise return nil
2029
// swallow IsNotModified error on StopContainer for already stopped container, simply call StartContainer
2130
s.logger.Debugf("restarting container: %s", cid)
22-
if err := s.nctlContainerSvc.StopContainer(ctx, con, options.Timeout); err != nil && !errdefs.IsNotModified(err) {
31+
if err := s.nctlContainerSvc.StopContainer(ctx, con.ID(), stopOptions); err != nil && !errdefs.IsNotModified(err) {
2332
s.logger.Errorf("Failed to stop container: %s. Error: %v", cid, err)
2433
return err
2534
}

internal/service/container/restart_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,17 @@ var _ = Describe("Container Restart API ", func() {
5151
options = ncTypes.ContainerRestartOptions{
5252
Timeout: &timeout,
5353
}
54+
5455
})
5556
Context("service", func() {
5657
It("should not return any error when Running", func() {
5758
// set up the mock to return a container that is in running state
59+
5860
cdClient.EXPECT().SearchContainer(gomock.Any(), cid).Return(
5961
[]containerd.Container{con}, nil).AnyTimes()
6062
//mock the nerdctl client to mock the restart container was successful without any error.
6163
ncClient.EXPECT().StartContainer(ctx, gomock.Any(), gomock.Any()).Return(nil)
62-
ncClient.EXPECT().StopContainer(ctx, con, &timeout).Return(nil)
64+
ncClient.EXPECT().StopContainer(ctx, con.ID(), gomock.Any()).Return(nil)
6365
gomock.InOrder(
6466
logger.EXPECT().Debugf("restarting container: %s", cid),
6567
logger.EXPECT().Debugf("successfully restarted: %s", cid),
@@ -73,7 +75,7 @@ var _ = Describe("Container Restart API ", func() {
7375
cdClient.EXPECT().SearchContainer(gomock.Any(), cid).Return(
7476
[]containerd.Container{con}, nil).AnyTimes()
7577
//mock the nerdctl client to mock the restart container was successful without any error.
76-
ncClient.EXPECT().StopContainer(ctx, con, &timeout).Return(errdefs.NewNotModified(fmt.Errorf("err")))
78+
ncClient.EXPECT().StopContainer(ctx, con.ID(), gomock.Any()).Return(errdefs.NewNotModified(fmt.Errorf("err")))
7779
ncClient.EXPECT().StartContainer(ctx, gomock.Any(), gomock.Any()).Return(nil)
7880
gomock.InOrder(
7981
logger.EXPECT().Debugf("restarting container: %s", cid),
@@ -88,7 +90,6 @@ var _ = Describe("Container Restart API ", func() {
8890
cdClient.EXPECT().SearchContainer(gomock.Any(), gomock.Any()).Return(
8991
[]containerd.Container{}, nil)
9092
logger.EXPECT().Debugf("no such container: %s", gomock.Any())
91-
9293
// service should return NotFound error
9394
err := service.Restart(ctx, cid, options)
9495
Expect(errdefs.IsNotFound(err)).Should(BeTrue())
@@ -109,7 +110,7 @@ var _ = Describe("Container Restart API ", func() {
109110
[]containerd.Container{con}, nil).AnyTimes()
110111

111112
expectedErr := fmt.Errorf("nerdctl error")
112-
ncClient.EXPECT().StopContainer(ctx, con, &timeout).Return(nil)
113+
ncClient.EXPECT().StopContainer(ctx, con.ID(), gomock.Any()).Return(nil)
113114
ncClient.EXPECT().StartContainer(ctx, gomock.Any(), gomock.Any()).Return(expectedErr)
114115
gomock.InOrder(
115116
logger.EXPECT().Debugf("restarting container: %s", cid),

internal/service/container/stop.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ package container
66
import (
77
"context"
88
"fmt"
9-
"time"
109

1110
containerd "github.com/containerd/containerd/v2/client"
11+
ncTypes "github.com/containerd/nerdctl/v2/pkg/api/types"
1212

1313
"github.com/runfinch/finch-daemon/pkg/errdefs"
1414
)
1515

1616
// Stop function stops a running container. It returns nil when it successfully stops the container.
17-
func (s *service) Stop(ctx context.Context, cid string, timeout *time.Duration) error {
17+
func (s *service) Stop(ctx context.Context, cid string, options ncTypes.ContainerStopOptions) error {
1818
con, err := s.getContainer(ctx, cid)
1919
if err != nil {
2020
return err
@@ -23,7 +23,7 @@ func (s *service) Stop(ctx context.Context, cid string, timeout *time.Duration)
2323
if s.isContainerStopped(ctx, con) {
2424
return errdefs.NewNotModified(fmt.Errorf("container is already stopped: %s", cid))
2525
}
26-
if err = s.nctlContainerSvc.StopContainer(ctx, con, timeout); err != nil {
26+
if err = s.nctlContainerSvc.StopContainer(ctx, con.ID(), options); err != nil {
2727
s.logger.Errorf("Failed to stop container: %s. Error: %v", cid, err)
2828
return err
2929
}

internal/service/container/stop_test.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
. "github.com/onsi/ginkgo/v2"
1313
. "github.com/onsi/gomega"
1414

15+
ncTypes "github.com/containerd/nerdctl/v2/pkg/api/types"
1516
"github.com/runfinch/finch-daemon/api/handlers/container"
1617
"github.com/runfinch/finch-daemon/mocks/mocks_archive"
1718
"github.com/runfinch/finch-daemon/mocks/mocks_backend"
@@ -32,6 +33,7 @@ var _ = Describe("Container Stop API ", func() {
3233
cid string
3334
tarExtractor *mocks_archive.MockTarExtractor
3435
service container.Service
36+
stopOptions ncTypes.ContainerStopOptions
3537
)
3638
BeforeEach(func() {
3739
ctx = context.Background()
@@ -43,6 +45,7 @@ var _ = Describe("Container Stop API ", func() {
4345
con = mocks_container.NewMockContainer(mockCtrl)
4446
con.EXPECT().ID().Return(cid).AnyTimes()
4547
tarExtractor = mocks_archive.NewMockTarExtractor(mockCtrl)
48+
stopOptions = ncTypes.ContainerStopOptions{}
4649

4750
service = NewService(cdClient, mockNerdctlService{ncClient, nil}, logger, nil, nil, tarExtractor)
4851
})
@@ -53,10 +56,10 @@ var _ = Describe("Container Stop API ", func() {
5356
cdClient.EXPECT().SearchContainer(gomock.Any(), gomock.Any()).Return(
5457
[]containerd.Container{con}, nil)
5558

56-
ncClient.EXPECT().StopContainer(ctx, con, gomock.Any())
59+
ncClient.EXPECT().StopContainer(ctx, con.ID(), gomock.Any())
5760
logger.EXPECT().Debugf("successfully stopped: %s", cid)
5861

59-
err := service.Stop(ctx, cid, nil)
62+
err := service.Stop(ctx, cid, stopOptions)
6063
Expect(err).Should(BeNil())
6164
})
6265
It("should return not found error", func() {
@@ -66,7 +69,7 @@ var _ = Describe("Container Stop API ", func() {
6669
logger.EXPECT().Debugf("no such container: %s", cid)
6770

6871
// service should return NotFound error
69-
err := service.Stop(ctx, cid, nil)
72+
err := service.Stop(ctx, cid, stopOptions)
7073
Expect(errdefs.IsNotFound(err)).Should(BeTrue())
7174
})
7275
It("should return multiple containers found error", func() {
@@ -76,7 +79,7 @@ var _ = Describe("Container Stop API ", func() {
7679
logger.EXPECT().Debugf("multiple IDs found with provided prefix: %s, total containers found: %d", cid, 2)
7780

7881
// service should return error
79-
err := service.Stop(ctx, cid, nil)
82+
err := service.Stop(ctx, cid, stopOptions)
8083
Expect(err).Should(Not(BeNil()))
8184
})
8285
It("should return not modified error as container is stopped already", func() {
@@ -86,7 +89,7 @@ var _ = Describe("Container Stop API ", func() {
8689
[]containerd.Container{con}, nil)
8790

8891
// service should return not modified error.
89-
err := service.Stop(ctx, cid, nil)
92+
err := service.Stop(ctx, cid, stopOptions)
9093
Expect(errdefs.IsNotModified(err)).Should(BeTrue())
9194
})
9295
It("should return not modified error as container is not running", func() {
@@ -96,7 +99,7 @@ var _ = Describe("Container Stop API ", func() {
9699
[]containerd.Container{con}, nil)
97100

98101
// service should return not modified error.
99-
err := service.Stop(ctx, cid, nil)
102+
err := service.Stop(ctx, cid, stopOptions)
100103
Expect(errdefs.IsNotModified(err)).Should(BeTrue())
101104
})
102105
It("should fail due to nerdctl client error", func() {
@@ -106,11 +109,11 @@ var _ = Describe("Container Stop API ", func() {
106109
[]containerd.Container{con}, nil)
107110

108111
expectedErr := fmt.Errorf("nerdctl error")
109-
ncClient.EXPECT().StopContainer(ctx, con, gomock.Any()).Return(expectedErr)
112+
ncClient.EXPECT().StopContainer(ctx, con.ID(), gomock.Any()).Return(expectedErr)
110113
logger.EXPECT().Errorf("Failed to stop container: %s. Error: %v", cid, expectedErr)
111114

112115
// service should return not modified error.
113-
err := service.Stop(ctx, cid, nil)
116+
err := service.Stop(ctx, cid, stopOptions)
114117
Expect(err).Should(Equal(expectedErr))
115118
})
116119
})

mocks/mocks_backend/nerdctlcontainersvc.go

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mocks/mocks_container/containersvc.go

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)