diff --git a/docker.go b/docker.go index 1493cd9682..c578796d75 100644 --- a/docker.go +++ b/docker.go @@ -1266,7 +1266,7 @@ func (p *DockerProvider) findContainerByName(ctx context.Context, name string) ( // Note that, 'name' filter will use regex to find the containers filter := filters.NewArgs(filters.Arg("name", fmt.Sprintf("^%s$", name))) - containers, err := p.client.ContainerList(ctx, container.ListOptions{Filters: filter}) + containers, err := p.client.ContainerList(ctx, container.ListOptions{All: true, Filters: filter}) if err != nil { return nil, fmt.Errorf("container list: %w", err) } @@ -1364,6 +1364,23 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain lifecycleHooks: []ContainerLifecycleHooks{combineContainerHooks(defaultHooks, req.LifecycleHooks)}, } + // If a container was stopped programmatically, we want to ensure the container + // is running again, but only if it is not paused, as it's not possible to start + // a paused container. The Docker Engine returns the "cannot start a paused container, + // try unpause instead" error. + switch c.State { + case "running": + // cannot re-start a running container, but we still need + // to call the startup hooks. + case "paused": + // TODO: we should unpause the container here. + return nil, fmt.Errorf("cannot start a paused container: %w", errors.ErrUnsupported) + default: + if err := dc.Start(ctx); err != nil { + return dc, fmt.Errorf("start container %s in state %s: %w", req.Name, c.State, err) + } + } + err = dc.startedHook(ctx) if err != nil { return nil, err diff --git a/reuse_test.go b/reuse_test.go new file mode 100644 index 0000000000..bb4ccddc7f --- /dev/null +++ b/reuse_test.go @@ -0,0 +1,73 @@ +package testcontainers_test + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/internal/core" +) + +func TestGenericContainer_stop_start_withReuse(t *testing.T) { + containerName := "my-nginx" + + req := testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: nginxAlpineImage, + ExposedPorts: []string{"8080/tcp"}, + Name: containerName, + }, + Reuse: true, + Started: true, + } + + ctr, err := testcontainers.GenericContainer(context.Background(), req) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + require.NotNil(t, ctr) + + err = ctr.Stop(context.Background(), nil) + require.NoError(t, err) + + // Run another container with same container name: + // The checks for the exposed ports must not fail when restarting the container. + ctr1, err := testcontainers.GenericContainer(context.Background(), req) + testcontainers.CleanupContainer(t, ctr1) + require.NoError(t, err) + require.NotNil(t, ctr1) +} + +func TestGenericContainer_pause_start_withReuse(t *testing.T) { + containerName := "my-nginx" + + req := testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: nginxAlpineImage, + ExposedPorts: []string{"8080/tcp"}, + Name: containerName, + }, + Reuse: true, + Started: true, + } + + ctr, err := testcontainers.GenericContainer(context.Background(), req) + testcontainers.CleanupContainer(t, ctr) + require.NoError(t, err) + require.NotNil(t, ctr) + + // Pause the container is not supported by our API, but we can do it manually + // by using the Docker client. + cli, err := core.NewClient(context.Background()) + require.NoError(t, err) + + err = cli.ContainerPause(context.Background(), ctr.GetContainerID()) + require.NoError(t, err) + + // Because the container is paused, it should not be possible to start it again. + ctr1, err := testcontainers.GenericContainer(context.Background(), req) + testcontainers.CleanupContainer(t, ctr1) + require.ErrorIs(t, err, errors.ErrUnsupported) +}