Skip to content

Commit 9da8340

Browse files
fix: handle stopped containers more gracefully when reuse is enabled (#3062)
* fix: handle stopped containers more gracefully when reuse is enabled * fix: start container if it's not running or paused * chore: proper container name * fix: more sensible comment * docs: add TODO comment about unpausing containers * chore: return an error for starting a paused container --------- Co-authored-by: Manuel de la Peña <[email protected]>
1 parent 37ce316 commit 9da8340

File tree

2 files changed

+91
-1
lines changed

2 files changed

+91
-1
lines changed

docker.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1266,7 +1266,7 @@ func (p *DockerProvider) findContainerByName(ctx context.Context, name string) (
12661266

12671267
// Note that, 'name' filter will use regex to find the containers
12681268
filter := filters.NewArgs(filters.Arg("name", fmt.Sprintf("^%s$", name)))
1269-
containers, err := p.client.ContainerList(ctx, container.ListOptions{Filters: filter})
1269+
containers, err := p.client.ContainerList(ctx, container.ListOptions{All: true, Filters: filter})
12701270
if err != nil {
12711271
return nil, fmt.Errorf("container list: %w", err)
12721272
}
@@ -1364,6 +1364,23 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain
13641364
lifecycleHooks: []ContainerLifecycleHooks{combineContainerHooks(defaultHooks, req.LifecycleHooks)},
13651365
}
13661366

1367+
// If a container was stopped programmatically, we want to ensure the container
1368+
// is running again, but only if it is not paused, as it's not possible to start
1369+
// a paused container. The Docker Engine returns the "cannot start a paused container,
1370+
// try unpause instead" error.
1371+
switch c.State {
1372+
case "running":
1373+
// cannot re-start a running container, but we still need
1374+
// to call the startup hooks.
1375+
case "paused":
1376+
// TODO: we should unpause the container here.
1377+
return nil, fmt.Errorf("cannot start a paused container: %w", errors.ErrUnsupported)
1378+
default:
1379+
if err := dc.Start(ctx); err != nil {
1380+
return dc, fmt.Errorf("start container %s in state %s: %w", req.Name, c.State, err)
1381+
}
1382+
}
1383+
13671384
err = dc.startedHook(ctx)
13681385
if err != nil {
13691386
return nil, err

reuse_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package testcontainers_test
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/testcontainers/testcontainers-go"
11+
"github.com/testcontainers/testcontainers-go/internal/core"
12+
)
13+
14+
func TestGenericContainer_stop_start_withReuse(t *testing.T) {
15+
containerName := "my-nginx"
16+
17+
req := testcontainers.GenericContainerRequest{
18+
ContainerRequest: testcontainers.ContainerRequest{
19+
Image: nginxAlpineImage,
20+
ExposedPorts: []string{"8080/tcp"},
21+
Name: containerName,
22+
},
23+
Reuse: true,
24+
Started: true,
25+
}
26+
27+
ctr, err := testcontainers.GenericContainer(context.Background(), req)
28+
testcontainers.CleanupContainer(t, ctr)
29+
require.NoError(t, err)
30+
require.NotNil(t, ctr)
31+
32+
err = ctr.Stop(context.Background(), nil)
33+
require.NoError(t, err)
34+
35+
// Run another container with same container name:
36+
// The checks for the exposed ports must not fail when restarting the container.
37+
ctr1, err := testcontainers.GenericContainer(context.Background(), req)
38+
testcontainers.CleanupContainer(t, ctr1)
39+
require.NoError(t, err)
40+
require.NotNil(t, ctr1)
41+
}
42+
43+
func TestGenericContainer_pause_start_withReuse(t *testing.T) {
44+
containerName := "my-nginx"
45+
46+
req := testcontainers.GenericContainerRequest{
47+
ContainerRequest: testcontainers.ContainerRequest{
48+
Image: nginxAlpineImage,
49+
ExposedPorts: []string{"8080/tcp"},
50+
Name: containerName,
51+
},
52+
Reuse: true,
53+
Started: true,
54+
}
55+
56+
ctr, err := testcontainers.GenericContainer(context.Background(), req)
57+
testcontainers.CleanupContainer(t, ctr)
58+
require.NoError(t, err)
59+
require.NotNil(t, ctr)
60+
61+
// Pause the container is not supported by our API, but we can do it manually
62+
// by using the Docker client.
63+
cli, err := core.NewClient(context.Background())
64+
require.NoError(t, err)
65+
66+
err = cli.ContainerPause(context.Background(), ctr.GetContainerID())
67+
require.NoError(t, err)
68+
69+
// Because the container is paused, it should not be possible to start it again.
70+
ctr1, err := testcontainers.GenericContainer(context.Background(), req)
71+
testcontainers.CleanupContainer(t, ctr1)
72+
require.ErrorIs(t, err, errors.ErrUnsupported)
73+
}

0 commit comments

Comments
 (0)