From 8632a795b4faa450665f5b7f83e4a93ce9dcd417 Mon Sep 17 00:00:00 2001 From: moogacs Date: Thu, 19 Dec 2024 23:52:44 +0100 Subject: [PATCH 01/13] feat(termination): make container termination timeout configurable --- cleanup.go | 48 +++++++++----------------------------------- container.go | 2 +- docker.go | 46 ++++++++++++++++++++++++++++++++++++------ docker_test.go | 21 +++++++++++++++++++ modules/etcd/etcd.go | 4 ++-- port_forwarding.go | 4 ++-- 6 files changed, 75 insertions(+), 50 deletions(-) diff --git a/cleanup.go b/cleanup.go index e2d52440b9..714e3d087d 100644 --- a/cleanup.go +++ b/cleanup.go @@ -2,7 +2,6 @@ package testcontainers import ( "context" - "errors" "fmt" "reflect" "time" @@ -16,21 +15,21 @@ type terminateOptions struct { } // TerminateOption is a type that represents an option for terminating a container. -type TerminateOption func(*terminateOptions) +type TerminateOption func(*DockerContainer) // StopContext returns a TerminateOption that sets the context. // Default: context.Background(). func StopContext(ctx context.Context) TerminateOption { - return func(c *terminateOptions) { - c.ctx = ctx + return func(c *DockerContainer) { + c.terminationOptions.ctx = ctx } } // StopTimeout returns a TerminateOption that sets the timeout. // Default: See [Container.Stop]. func StopTimeout(timeout time.Duration) TerminateOption { - return func(c *terminateOptions) { - c.timeout = &timeout + return func(c *DockerContainer) { + c.terminationOptions.timeout = &timeout } } @@ -39,8 +38,8 @@ func StopTimeout(timeout time.Duration) TerminateOption { // which are not removed by default. // Default: nil. func RemoveVolumes(volumes ...string) TerminateOption { - return func(c *terminateOptions) { - c.volumes = volumes + return func(c *DockerContainer) { + c.terminationOptions.volumes = volumes } } @@ -54,41 +53,12 @@ func TerminateContainer(container Container, options ...TerminateOption) error { return nil } - c := &terminateOptions{ - ctx: context.Background(), - } - - for _, opt := range options { - opt(c) - } - - // TODO: Add a timeout when terminate supports it. - err := container.Terminate(c.ctx) + err := container.Terminate(context.Background(), options...) if !isCleanupSafe(err) { return fmt.Errorf("terminate: %w", err) } - // Remove additional volumes if any. - if len(c.volumes) == 0 { - return nil - } - - client, err := NewDockerClientWithOpts(c.ctx) - if err != nil { - return fmt.Errorf("docker client: %w", err) - } - - defer client.Close() - - // Best effort to remove all volumes. - var errs []error - for _, volume := range c.volumes { - if errRemove := client.VolumeRemove(c.ctx, volume, true); errRemove != nil { - errs = append(errs, fmt.Errorf("volume remove %q: %w", volume, errRemove)) - } - } - - return errors.Join(errs...) + return nil } // isNil returns true if val is nil or an nil instance false otherwise. diff --git a/container.go b/container.go index 35be60fb81..50fc656e7e 100644 --- a/container.go +++ b/container.go @@ -50,7 +50,7 @@ type Container interface { Stop(context.Context, *time.Duration) error // stop the container // Terminate stops and removes the container and its image if it was built and not flagged as kept. - Terminate(ctx context.Context) error + Terminate(ctx context.Context, opts ...TerminateOption) error Logs(context.Context) (io.ReadCloser, error) // Get logs of the container FollowOutput(LogConsumer) // Deprecated: it will be removed in the next major release diff --git a/docker.go b/docker.go index 01b3d3d4d2..8e9ff6a458 100644 --- a/docker.go +++ b/docker.go @@ -89,6 +89,7 @@ type DockerContainer struct { logProductionCtx context.Context logProductionTimeout *time.Duration + terminationOptions terminateOptions logger Logging lifecycleHooks []ContainerLifecycleHooks @@ -296,6 +297,13 @@ func (c *DockerContainer) Stop(ctx context.Context, timeout *time.Duration) erro return nil } +// WithTerminateTimeout is a functional option that sets the timeout for the container termination. +func WithTerminateTimeout(timeout time.Duration) TerminateOption { + return func(c *DockerContainer) { + c.terminationOptions.timeout = &timeout + } +} + // Terminate calls stops and then removes the container including its volumes. // If its image was built it and all child images are also removed unless // the [FromDockerfile.KeepImage] on the [ContainerRequest] was set to true. @@ -303,12 +311,19 @@ func (c *DockerContainer) Stop(ctx context.Context, timeout *time.Duration) erro // The following hooks are called in order: // - [ContainerLifecycleHooks.PreTerminates] // - [ContainerLifecycleHooks.PostTerminates] -func (c *DockerContainer) Terminate(ctx context.Context) error { - // ContainerRemove hardcodes stop timeout to 3 seconds which is too short - // to ensure that child containers are stopped so we manually call stop. - // TODO: make this configurable via a functional option. - timeout := 10 * time.Second - err := c.Stop(ctx, &timeout) +// +// Default: timeout is 10 seconds. +func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption) error { + if len(opts) == 0 { + d := 10 * time.Second + c.terminationOptions.timeout = &d + } + + for _, opt := range opts { + opt(c) + } + + err := c.Stop(ctx, c.terminationOptions.timeout) if err != nil && !isCleanupSafe(err) { return fmt.Errorf("stop: %w", err) } @@ -343,6 +358,25 @@ func (c *DockerContainer) Terminate(ctx context.Context) error { c.sessionID = "" c.isRunning = false + // Remove additional volumes if any. + if len(c.terminationOptions.volumes) == 0 { + return nil + } + + client, err := NewDockerClientWithOpts(ctx) + if err != nil { + return fmt.Errorf("docker client: %w", err) + } + + defer client.Close() + + // Best effort to remove all volumes. + for _, volume := range c.terminationOptions.volumes { + if errRemove := client.VolumeRemove(ctx, volume, true); errRemove != nil { + errs = append(errs, fmt.Errorf("volume remove %q: %w", volume, errRemove)) + } + } + return errors.Join(errs...) } diff --git a/docker_test.go b/docker_test.go index 8fcd60c558..48d7e257fc 100644 --- a/docker_test.go +++ b/docker_test.go @@ -281,6 +281,27 @@ func TestContainerStateAfterTermination(t *testing.T) { require.Nil(t, state, "expected nil container inspect.") }) + t.Run("Nil State after termination with timeout", func(t *testing.T) { + ctx := context.Background() + nginx, err := createContainerFn(ctx) + require.NoError(t, err) + + err = nginx.Start(ctx) + require.NoError(t, err, "expected no error from container start.") + + state, err := nginx.State(ctx) + require.NoError(t, err, "expected no error from container inspect.") + require.NotNil(t, state, "expected non nil container inspect.") + require.True(t, state.Running, "expected container state to be running.") + + err = nginx.Terminate(ctx, WithTerminateTimeout(5*time.Microsecond)) + require.NoError(t, err) + + state, err = nginx.State(ctx) + require.Error(t, err, "expected error from container inspect.") + require.Nil(t, state, "expected nil container inspect.") + }) + t.Run("Nil State after termination if raw as already set", func(t *testing.T) { ctx := context.Background() nginx, err := createContainerFn(ctx) diff --git a/modules/etcd/etcd.go b/modules/etcd/etcd.go index 7ea78b4385..9d89e1813a 100644 --- a/modules/etcd/etcd.go +++ b/modules/etcd/etcd.go @@ -29,12 +29,12 @@ type EtcdContainer struct { // Terminate terminates the etcd container, its child nodes, and the network in which the cluster is running // to communicate between the nodes. -func (c *EtcdContainer) Terminate(ctx context.Context) error { +func (c *EtcdContainer) Terminate(ctx context.Context, opts ...testcontainers.TerminateOption) error { var errs []error // child nodes has no other children for i, child := range c.childNodes { - if err := child.Terminate(ctx); err != nil { + if err := child.Terminate(ctx, opts...); err != nil { errs = append(errs, fmt.Errorf("terminate child node(%d): %w", i, err)) } } diff --git a/port_forwarding.go b/port_forwarding.go index bb6bae2393..3411ff0c1f 100644 --- a/port_forwarding.go +++ b/port_forwarding.go @@ -225,10 +225,10 @@ type sshdContainer struct { } // Terminate stops the container and closes the SSH session -func (sshdC *sshdContainer) Terminate(ctx context.Context) error { +func (sshdC *sshdContainer) Terminate(ctx context.Context, opts ...TerminateOption) error { return errors.Join( sshdC.closePorts(), - sshdC.Container.Terminate(ctx), + sshdC.Container.Terminate(ctx, opts...), ) } From ac8ce586a4e675f328aa5116f142c4c9affd657d Mon Sep 17 00:00:00 2001 From: moogacs Date: Sat, 21 Dec 2024 16:58:04 +0100 Subject: [PATCH 02/13] revert TerminateOption to terminateOptions, seperate from DockerContainer and introduce configuration test --- cleanup.go | 14 +++++++------- docker.go | 26 ++++++++++++++++---------- docker_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/cleanup.go b/cleanup.go index 714e3d087d..ccdcc31528 100644 --- a/cleanup.go +++ b/cleanup.go @@ -15,21 +15,21 @@ type terminateOptions struct { } // TerminateOption is a type that represents an option for terminating a container. -type TerminateOption func(*DockerContainer) +type TerminateOption func(*terminateOptions) // StopContext returns a TerminateOption that sets the context. // Default: context.Background(). func StopContext(ctx context.Context) TerminateOption { - return func(c *DockerContainer) { - c.terminationOptions.ctx = ctx + return func(c *terminateOptions) { + c.ctx = ctx } } // StopTimeout returns a TerminateOption that sets the timeout. // Default: See [Container.Stop]. func StopTimeout(timeout time.Duration) TerminateOption { - return func(c *DockerContainer) { - c.terminationOptions.timeout = &timeout + return func(c *terminateOptions) { + c.timeout = &timeout } } @@ -38,8 +38,8 @@ func StopTimeout(timeout time.Duration) TerminateOption { // which are not removed by default. // Default: nil. func RemoveVolumes(volumes ...string) TerminateOption { - return func(c *DockerContainer) { - c.terminationOptions.volumes = volumes + return func(c *terminateOptions) { + c.volumes = volumes } } diff --git a/docker.go b/docker.go index 8e9ff6a458..ef0178a326 100644 --- a/docker.go +++ b/docker.go @@ -89,7 +89,6 @@ type DockerContainer struct { logProductionCtx context.Context logProductionTimeout *time.Duration - terminationOptions terminateOptions logger Logging lifecycleHooks []ContainerLifecycleHooks @@ -299,8 +298,15 @@ func (c *DockerContainer) Stop(ctx context.Context, timeout *time.Duration) erro // WithTerminateTimeout is a functional option that sets the timeout for the container termination. func WithTerminateTimeout(timeout time.Duration) TerminateOption { - return func(c *DockerContainer) { - c.terminationOptions.timeout = &timeout + return func(c *terminateOptions) { + c.timeout = &timeout + } +} + +// WithTerminateVolumes is a functional option that sets the volumes for the container termination. +func WithTerminateVolumes(volumes ...string) TerminateOption { + return func(opts *terminateOptions) { + opts.volumes = volumes } } @@ -314,16 +320,16 @@ func WithTerminateTimeout(timeout time.Duration) TerminateOption { // // Default: timeout is 10 seconds. func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption) error { - if len(opts) == 0 { - d := 10 * time.Second - c.terminationOptions.timeout = &d + defaultTimeout := 10 * time.Second + options := &terminateOptions{ + timeout: &defaultTimeout, } for _, opt := range opts { - opt(c) + opt(options) } - err := c.Stop(ctx, c.terminationOptions.timeout) + err := c.Stop(ctx, options.timeout) if err != nil && !isCleanupSafe(err) { return fmt.Errorf("stop: %w", err) } @@ -359,7 +365,7 @@ func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption c.isRunning = false // Remove additional volumes if any. - if len(c.terminationOptions.volumes) == 0 { + if len(options.volumes) == 0 { return nil } @@ -371,7 +377,7 @@ func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption defer client.Close() // Best effort to remove all volumes. - for _, volume := range c.terminationOptions.volumes { + for _, volume := range options.volumes { if errRemove := client.VolumeRemove(ctx, volume, true); errRemove != nil { errs = append(errs, fmt.Errorf("volume remove %q: %w", volume, errRemove)) } diff --git a/docker_test.go b/docker_test.go index 48d7e257fc..c4e49e4025 100644 --- a/docker_test.go +++ b/docker_test.go @@ -1110,6 +1110,56 @@ func TestContainerCreationWithVolumeAndFileWritingToIt(t *testing.T) { require.NoError(t, err) } +func TestContainerCreationWithVolumeCleaning(t *testing.T) { + absPath, err := filepath.Abs(filepath.Join(".", "testdata", "hello.sh")) + require.NoError(t, err) + ctx, cnl := context.WithTimeout(context.Background(), 30*time.Second) + defer cnl() + + // Create the volume. + volumeName := "volumeName" + + // Create the container that writes into the mounted volume. + bashC, err := GenericContainer(ctx, GenericContainerRequest{ + ProviderType: providerType, + ContainerRequest: ContainerRequest{ + Image: "bash:5.2.26", + Files: []ContainerFile{ + { + HostFilePath: absPath, + ContainerFilePath: "/hello.sh", + }, + }, + Mounts: Mounts(VolumeMount(volumeName, "/data")), + Cmd: []string{"bash", "/hello.sh"}, + WaitingFor: wait.ForLog("done"), + }, + Started: true, + }) + require.NoError(t, err) + err = bashC.Terminate(ctx, WithTerminateVolumes(volumeName)) + require.NoError(t, err) +} + +func TestContainerTerminationOptions(t *testing.T) { + volumeName := "volumeName" + definedVolumeOpt := &terminateOptions{} + volumeOpt := WithTerminateVolumes(volumeName) + volumeOpt(definedVolumeOpt) + require.Equal(t, definedVolumeOpt.volumes, []string{volumeName}) + + defaultTimeout := 10 * time.Second + definedTimeoutOpt := &terminateOptions{ + timeout: &defaultTimeout, + } + + configuredTimeout := 1 * time.Second + + timeoutOpt := WithTerminateTimeout(1 * time.Second) + timeoutOpt(definedTimeoutOpt) + require.Equal(t, *definedTimeoutOpt.timeout, configuredTimeout) +} + func TestContainerWithTmpFs(t *testing.T) { ctx := context.Background() req := ContainerRequest{ From 4cdb318e2565e1b7bb00d3213c77db8bd0e6e80a Mon Sep 17 00:00:00 2001 From: moogacs Date: Sat, 21 Dec 2024 17:05:29 +0100 Subject: [PATCH 03/13] rename to defaultOptions and pass ctx --- docker.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docker.go b/docker.go index ef0178a326..8e95e10da0 100644 --- a/docker.go +++ b/docker.go @@ -321,15 +321,16 @@ func WithTerminateVolumes(volumes ...string) TerminateOption { // Default: timeout is 10 seconds. func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption) error { defaultTimeout := 10 * time.Second - options := &terminateOptions{ + defaultOptions := &terminateOptions{ timeout: &defaultTimeout, + ctx: ctx, } for _, opt := range opts { - opt(options) + opt(defaultOptions) } - err := c.Stop(ctx, options.timeout) + err := c.Stop(defaultOptions.ctx, defaultOptions.timeout) if err != nil && !isCleanupSafe(err) { return fmt.Errorf("stop: %w", err) } @@ -365,7 +366,7 @@ func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption c.isRunning = false // Remove additional volumes if any. - if len(options.volumes) == 0 { + if len(defaultOptions.volumes) == 0 { return nil } @@ -377,7 +378,7 @@ func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption defer client.Close() // Best effort to remove all volumes. - for _, volume := range options.volumes { + for _, volume := range defaultOptions.volumes { if errRemove := client.VolumeRemove(ctx, volume, true); errRemove != nil { errs = append(errs, fmt.Errorf("volume remove %q: %w", volume, errRemove)) } From d994d55661d582f7156b623278355dc628758640 Mon Sep 17 00:00:00 2001 From: moogacs Date: Mon, 23 Dec 2024 12:48:20 +0100 Subject: [PATCH 04/13] address comments --- cleanup.go | 27 +++++++++++++++------------ docker.go | 22 +++++++++++----------- docker_test.go | 23 ++++++++--------------- modules/etcd/etcd.go | 2 +- 4 files changed, 35 insertions(+), 39 deletions(-) diff --git a/cleanup.go b/cleanup.go index ccdcc31528..4de5247a8f 100644 --- a/cleanup.go +++ b/cleanup.go @@ -7,29 +7,32 @@ import ( "time" ) -// terminateOptions is a type that holds the options for terminating a container. -type terminateOptions struct { - ctx context.Context - timeout *time.Duration - volumes []string +// DefaultTimeout for termination +var DefaultTimeout = 10 * time.Second + +// TerminateOptions is a type that holds the options for terminating a container. +type TerminateOptions struct { + Context context.Context + Timeout *time.Duration + Volumes []string } // TerminateOption is a type that represents an option for terminating a container. -type TerminateOption func(*terminateOptions) +type TerminateOption func(*TerminateOptions) // StopContext returns a TerminateOption that sets the context. // Default: context.Background(). func StopContext(ctx context.Context) TerminateOption { - return func(c *terminateOptions) { - c.ctx = ctx + return func(c *TerminateOptions) { + c.Context = ctx } } // StopTimeout returns a TerminateOption that sets the timeout. // Default: See [Container.Stop]. func StopTimeout(timeout time.Duration) TerminateOption { - return func(c *terminateOptions) { - c.timeout = &timeout + return func(c *TerminateOptions) { + c.Timeout = &timeout } } @@ -38,8 +41,8 @@ func StopTimeout(timeout time.Duration) TerminateOption { // which are not removed by default. // Default: nil. func RemoveVolumes(volumes ...string) TerminateOption { - return func(c *terminateOptions) { - c.volumes = volumes + return func(c *TerminateOptions) { + c.Volumes = volumes } } diff --git a/docker.go b/docker.go index 8e95e10da0..5bb65f18ae 100644 --- a/docker.go +++ b/docker.go @@ -298,15 +298,15 @@ func (c *DockerContainer) Stop(ctx context.Context, timeout *time.Duration) erro // WithTerminateTimeout is a functional option that sets the timeout for the container termination. func WithTerminateTimeout(timeout time.Duration) TerminateOption { - return func(c *terminateOptions) { - c.timeout = &timeout + return func(c *TerminateOptions) { + c.Timeout = &timeout } } // WithTerminateVolumes is a functional option that sets the volumes for the container termination. func WithTerminateVolumes(volumes ...string) TerminateOption { - return func(opts *terminateOptions) { - opts.volumes = volumes + return func(opts *TerminateOptions) { + opts.Volumes = volumes } } @@ -320,17 +320,16 @@ func WithTerminateVolumes(volumes ...string) TerminateOption { // // Default: timeout is 10 seconds. func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption) error { - defaultTimeout := 10 * time.Second - defaultOptions := &terminateOptions{ - timeout: &defaultTimeout, - ctx: ctx, + defaultOptions := &TerminateOptions{ + Timeout: &DefaultTimeout, + Context: ctx, } for _, opt := range opts { opt(defaultOptions) } - err := c.Stop(defaultOptions.ctx, defaultOptions.timeout) + err := c.Stop(defaultOptions.Context, defaultOptions.Timeout) if err != nil && !isCleanupSafe(err) { return fmt.Errorf("stop: %w", err) } @@ -366,7 +365,8 @@ func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption c.isRunning = false // Remove additional volumes if any. - if len(defaultOptions.volumes) == 0 { + // TODO: simplify this when when perform the client refactor. + if len(defaultOptions.Volumes) == 0 { return nil } @@ -378,7 +378,7 @@ func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption defer client.Close() // Best effort to remove all volumes. - for _, volume := range defaultOptions.volumes { + for _, volume := range defaultOptions.Volumes { if errRemove := client.VolumeRemove(ctx, volume, true); errRemove != nil { errs = append(errs, fmt.Errorf("volume remove %q: %w", volume, errRemove)) } diff --git a/docker_test.go b/docker_test.go index c4e49e4025..3950bf0ea3 100644 --- a/docker_test.go +++ b/docker_test.go @@ -281,7 +281,7 @@ func TestContainerStateAfterTermination(t *testing.T) { require.Nil(t, state, "expected nil container inspect.") }) - t.Run("Nil State after termination with timeout", func(t *testing.T) { + t.Run("termination-timeout", func(t *testing.T) { ctx := context.Background() nginx, err := createContainerFn(ctx) require.NoError(t, err) @@ -289,17 +289,8 @@ func TestContainerStateAfterTermination(t *testing.T) { err = nginx.Start(ctx) require.NoError(t, err, "expected no error from container start.") - state, err := nginx.State(ctx) - require.NoError(t, err, "expected no error from container inspect.") - require.NotNil(t, state, "expected non nil container inspect.") - require.True(t, state.Running, "expected container state to be running.") - err = nginx.Terminate(ctx, WithTerminateTimeout(5*time.Microsecond)) require.NoError(t, err) - - state, err = nginx.State(ctx) - require.Error(t, err, "expected error from container inspect.") - require.Nil(t, state, "expected nil container inspect.") }) t.Run("Nil State after termination if raw as already set", func(t *testing.T) { @@ -1098,6 +1089,7 @@ func TestContainerCreationWithVolumeAndFileWritingToIt(t *testing.T) { { HostFilePath: absPath, ContainerFilePath: "/hello.sh", + FileMode: 700, }, }, Mounts: Mounts(VolumeMount(volumeName, "/data")), @@ -1138,26 +1130,27 @@ func TestContainerCreationWithVolumeCleaning(t *testing.T) { }) require.NoError(t, err) err = bashC.Terminate(ctx, WithTerminateVolumes(volumeName)) + CleanupContainer(t, bashC, RemoveVolumes(volumeName)) require.NoError(t, err) } func TestContainerTerminationOptions(t *testing.T) { volumeName := "volumeName" - definedVolumeOpt := &terminateOptions{} + definedVolumeOpt := &TerminateOptions{} volumeOpt := WithTerminateVolumes(volumeName) volumeOpt(definedVolumeOpt) - require.Equal(t, definedVolumeOpt.volumes, []string{volumeName}) + require.Equal(t, definedVolumeOpt.Volumes, []string{volumeName}) defaultTimeout := 10 * time.Second - definedTimeoutOpt := &terminateOptions{ - timeout: &defaultTimeout, + definedTimeoutOpt := &TerminateOptions{ + Timeout: &defaultTimeout, } configuredTimeout := 1 * time.Second timeoutOpt := WithTerminateTimeout(1 * time.Second) timeoutOpt(definedTimeoutOpt) - require.Equal(t, *definedTimeoutOpt.timeout, configuredTimeout) + require.Equal(t, *definedTimeoutOpt.Timeout, configuredTimeout) } func TestContainerWithTmpFs(t *testing.T) { diff --git a/modules/etcd/etcd.go b/modules/etcd/etcd.go index 9d89e1813a..a715150bf1 100644 --- a/modules/etcd/etcd.go +++ b/modules/etcd/etcd.go @@ -40,7 +40,7 @@ func (c *EtcdContainer) Terminate(ctx context.Context, opts ...testcontainers.Te } if c.Container != nil { - if err := c.Container.Terminate(ctx); err != nil { + if err := c.Container.Terminate(ctx, opts...); err != nil { errs = append(errs, fmt.Errorf("terminate cluster node: %w", err)) } } From c38f36bb698d1e66f14f18f67a1c9b848df7f741 Mon Sep 17 00:00:00 2001 From: moogacs Date: Wed, 25 Dec 2024 12:53:18 +0100 Subject: [PATCH 05/13] resolve more suggestions --- cleanup.go | 57 ++++++++++++++++++++++++++++++++++++++++++++------ docker.go | 40 +++++++---------------------------- docker_test.go | 42 +++++++++++++++++++++++-------------- 3 files changed, 85 insertions(+), 54 deletions(-) diff --git a/cleanup.go b/cleanup.go index 4de5247a8f..74d275910c 100644 --- a/cleanup.go +++ b/cleanup.go @@ -2,6 +2,7 @@ package testcontainers import ( "context" + "errors" "fmt" "reflect" "time" @@ -12,19 +13,63 @@ var DefaultTimeout = 10 * time.Second // TerminateOptions is a type that holds the options for terminating a container. type TerminateOptions struct { - Context context.Context - Timeout *time.Duration - Volumes []string + ctx context.Context + stopTimeout *time.Duration + volumes []string } // TerminateOption is a type that represents an option for terminating a container. type TerminateOption func(*TerminateOptions) +// NewTerminateOptions returns a fully initialised TerminateOptions. +// Defaults: StopTimeout: 10 seconds. +func NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *TerminateOptions { + options := &TerminateOptions{ + stopTimeout: &DefaultTimeout, + ctx: ctx, + } + for _, opt := range opts { + opt(options) + } + return options +} + +// Context returns the context to use duration a Terminate. +func (o *TerminateOptions) Context() context.Context { + return o.ctx +} + +// StopTimeout returns the stop timeout to use duration a Terminate. +func (o *TerminateOptions) StopTimeout() *time.Duration { + return o.stopTimeout +} + +// Cleanup performs any clean up needed +func (o *TerminateOptions) Cleanup() error { + // TODO: simplify this when when perform the client refactor. + if len(o.volumes) == 0 { + return nil + } + client, err := NewDockerClientWithOpts(o.ctx) + if err != nil { + return fmt.Errorf("docker client: %w", err) + } + defer client.Close() + // Best effort to remove all volumes. + var errs []error + for _, volume := range o.volumes { + if errRemove := client.VolumeRemove(o.ctx, volume, true); errRemove != nil { + errs = append(errs, fmt.Errorf("volume remove %q: %w", volume, errRemove)) + } + } + return errors.Join(errs...) +} + // StopContext returns a TerminateOption that sets the context. // Default: context.Background(). func StopContext(ctx context.Context) TerminateOption { return func(c *TerminateOptions) { - c.Context = ctx + c.ctx = ctx } } @@ -32,7 +77,7 @@ func StopContext(ctx context.Context) TerminateOption { // Default: See [Container.Stop]. func StopTimeout(timeout time.Duration) TerminateOption { return func(c *TerminateOptions) { - c.Timeout = &timeout + c.stopTimeout = &timeout } } @@ -42,7 +87,7 @@ func StopTimeout(timeout time.Duration) TerminateOption { // Default: nil. func RemoveVolumes(volumes ...string) TerminateOption { return func(c *TerminateOptions) { - c.Volumes = volumes + c.volumes = volumes } } diff --git a/docker.go b/docker.go index 5bb65f18ae..da9e959f8b 100644 --- a/docker.go +++ b/docker.go @@ -296,17 +296,17 @@ func (c *DockerContainer) Stop(ctx context.Context, timeout *time.Duration) erro return nil } -// WithTerminateTimeout is a functional option that sets the timeout for the container termination. -func WithTerminateTimeout(timeout time.Duration) TerminateOption { +// WithStopTimeout is a functional option that sets the timeout for the container termination. +func WithStopTimeout(timeout time.Duration) TerminateOption { return func(c *TerminateOptions) { - c.Timeout = &timeout + c.stopTimeout = &timeout } } // WithTerminateVolumes is a functional option that sets the volumes for the container termination. func WithTerminateVolumes(volumes ...string) TerminateOption { return func(opts *TerminateOptions) { - opts.Volumes = volumes + opts.volumes = volumes } } @@ -320,16 +320,8 @@ func WithTerminateVolumes(volumes ...string) TerminateOption { // // Default: timeout is 10 seconds. func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption) error { - defaultOptions := &TerminateOptions{ - Timeout: &DefaultTimeout, - Context: ctx, - } - - for _, opt := range opts { - opt(defaultOptions) - } - - err := c.Stop(defaultOptions.Context, defaultOptions.Timeout) + options := NewTerminateOptions(ctx, opts...) + err := c.Stop(options.Context(), options.StopTimeout()) if err != nil && !isCleanupSafe(err) { return fmt.Errorf("stop: %w", err) } @@ -364,24 +356,8 @@ func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption c.sessionID = "" c.isRunning = false - // Remove additional volumes if any. - // TODO: simplify this when when perform the client refactor. - if len(defaultOptions.Volumes) == 0 { - return nil - } - - client, err := NewDockerClientWithOpts(ctx) - if err != nil { - return fmt.Errorf("docker client: %w", err) - } - - defer client.Close() - - // Best effort to remove all volumes. - for _, volume := range defaultOptions.Volumes { - if errRemove := client.VolumeRemove(ctx, volume, true); errRemove != nil { - errs = append(errs, fmt.Errorf("volume remove %q: %w", volume, errRemove)) - } + if err := options.Cleanup(); err != nil { + errs = append(errs, err) } return errors.Join(errs...) diff --git a/docker_test.go b/docker_test.go index 3950bf0ea3..006734b7b1 100644 --- a/docker_test.go +++ b/docker_test.go @@ -289,7 +289,7 @@ func TestContainerStateAfterTermination(t *testing.T) { err = nginx.Start(ctx) require.NoError(t, err, "expected no error from container start.") - err = nginx.Terminate(ctx, WithTerminateTimeout(5*time.Microsecond)) + err = nginx.Terminate(ctx, WithStopTimeout(5*time.Microsecond)) require.NoError(t, err) }) @@ -1135,22 +1135,32 @@ func TestContainerCreationWithVolumeCleaning(t *testing.T) { } func TestContainerTerminationOptions(t *testing.T) { - volumeName := "volumeName" - definedVolumeOpt := &TerminateOptions{} - volumeOpt := WithTerminateVolumes(volumeName) - volumeOpt(definedVolumeOpt) - require.Equal(t, definedVolumeOpt.Volumes, []string{volumeName}) - - defaultTimeout := 10 * time.Second - definedTimeoutOpt := &TerminateOptions{ - Timeout: &defaultTimeout, - } - - configuredTimeout := 1 * time.Second + t.Run("volumes", func(t *testing.T) { + var options TerminateOptions + WithTerminateVolumes("vol1", "vol2")(&options) + require.Equal(t, TerminateOptions{ + volumes: []string{"vol1", "vol2"}, + }, options) + }) + t.Run("stop-timeout", func(t *testing.T) { + var options TerminateOptions + timeout := 11 * time.Second + WithStopTimeout(timeout)(&options) + require.Equal(t, TerminateOptions{ + stopTimeout: &timeout, + }, options) + }) - timeoutOpt := WithTerminateTimeout(1 * time.Second) - timeoutOpt(definedTimeoutOpt) - require.Equal(t, *definedTimeoutOpt.Timeout, configuredTimeout) + t.Run("all", func(t *testing.T) { + var options TerminateOptions + timeout := 9 * time.Second + WithStopTimeout(timeout)(&options) + WithTerminateVolumes("vol1", "vol2")(&options) + require.Equal(t, TerminateOptions{ + stopTimeout: &timeout, + volumes: []string{"vol1", "vol2"}, + }, options) + }) } func TestContainerWithTmpFs(t *testing.T) { From 1377520b31ba1d0d8f034def377c76e4dd7a6d8a Mon Sep 17 00:00:00 2001 From: moogacs Date: Wed, 25 Dec 2024 12:55:53 +0100 Subject: [PATCH 06/13] don't export terminateOptions --- cleanup.go | 22 +++++++++++----------- docker.go | 4 ++-- docker_test.go | 12 ++++++------ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/cleanup.go b/cleanup.go index 74d275910c..7a6564fc15 100644 --- a/cleanup.go +++ b/cleanup.go @@ -11,20 +11,20 @@ import ( // DefaultTimeout for termination var DefaultTimeout = 10 * time.Second -// TerminateOptions is a type that holds the options for terminating a container. -type TerminateOptions struct { +// terminateOptions is a type that holds the options for terminating a container. +type terminateOptions struct { ctx context.Context stopTimeout *time.Duration volumes []string } // TerminateOption is a type that represents an option for terminating a container. -type TerminateOption func(*TerminateOptions) +type TerminateOption func(*terminateOptions) // NewTerminateOptions returns a fully initialised TerminateOptions. // Defaults: StopTimeout: 10 seconds. -func NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *TerminateOptions { - options := &TerminateOptions{ +func NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *terminateOptions { + options := &terminateOptions{ stopTimeout: &DefaultTimeout, ctx: ctx, } @@ -35,17 +35,17 @@ func NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *Terminat } // Context returns the context to use duration a Terminate. -func (o *TerminateOptions) Context() context.Context { +func (o *terminateOptions) Context() context.Context { return o.ctx } // StopTimeout returns the stop timeout to use duration a Terminate. -func (o *TerminateOptions) StopTimeout() *time.Duration { +func (o *terminateOptions) StopTimeout() *time.Duration { return o.stopTimeout } // Cleanup performs any clean up needed -func (o *TerminateOptions) Cleanup() error { +func (o *terminateOptions) Cleanup() error { // TODO: simplify this when when perform the client refactor. if len(o.volumes) == 0 { return nil @@ -68,7 +68,7 @@ func (o *TerminateOptions) Cleanup() error { // StopContext returns a TerminateOption that sets the context. // Default: context.Background(). func StopContext(ctx context.Context) TerminateOption { - return func(c *TerminateOptions) { + return func(c *terminateOptions) { c.ctx = ctx } } @@ -76,7 +76,7 @@ func StopContext(ctx context.Context) TerminateOption { // StopTimeout returns a TerminateOption that sets the timeout. // Default: See [Container.Stop]. func StopTimeout(timeout time.Duration) TerminateOption { - return func(c *TerminateOptions) { + return func(c *terminateOptions) { c.stopTimeout = &timeout } } @@ -86,7 +86,7 @@ func StopTimeout(timeout time.Duration) TerminateOption { // which are not removed by default. // Default: nil. func RemoveVolumes(volumes ...string) TerminateOption { - return func(c *TerminateOptions) { + return func(c *terminateOptions) { c.volumes = volumes } } diff --git a/docker.go b/docker.go index da9e959f8b..398ec7d7fc 100644 --- a/docker.go +++ b/docker.go @@ -298,14 +298,14 @@ func (c *DockerContainer) Stop(ctx context.Context, timeout *time.Duration) erro // WithStopTimeout is a functional option that sets the timeout for the container termination. func WithStopTimeout(timeout time.Duration) TerminateOption { - return func(c *TerminateOptions) { + return func(c *terminateOptions) { c.stopTimeout = &timeout } } // WithTerminateVolumes is a functional option that sets the volumes for the container termination. func WithTerminateVolumes(volumes ...string) TerminateOption { - return func(opts *TerminateOptions) { + return func(opts *terminateOptions) { opts.volumes = volumes } } diff --git a/docker_test.go b/docker_test.go index 006734b7b1..8665ad2a00 100644 --- a/docker_test.go +++ b/docker_test.go @@ -1136,27 +1136,27 @@ func TestContainerCreationWithVolumeCleaning(t *testing.T) { func TestContainerTerminationOptions(t *testing.T) { t.Run("volumes", func(t *testing.T) { - var options TerminateOptions + var options terminateOptions WithTerminateVolumes("vol1", "vol2")(&options) - require.Equal(t, TerminateOptions{ + require.Equal(t, terminateOptions{ volumes: []string{"vol1", "vol2"}, }, options) }) t.Run("stop-timeout", func(t *testing.T) { - var options TerminateOptions + var options terminateOptions timeout := 11 * time.Second WithStopTimeout(timeout)(&options) - require.Equal(t, TerminateOptions{ + require.Equal(t, terminateOptions{ stopTimeout: &timeout, }, options) }) t.Run("all", func(t *testing.T) { - var options TerminateOptions + var options terminateOptions timeout := 9 * time.Second WithStopTimeout(timeout)(&options) WithTerminateVolumes("vol1", "vol2")(&options) - require.Equal(t, TerminateOptions{ + require.Equal(t, terminateOptions{ stopTimeout: &timeout, volumes: []string{"vol1", "vol2"}, }, options) From 6f16fa0490bc918b1cd3861c77dd4d35577873ad Mon Sep 17 00:00:00 2001 From: moogacs Date: Sun, 29 Dec 2024 13:55:13 +0100 Subject: [PATCH 07/13] address more comments --- cleanup.go | 22 +++++++++++----------- docker.go | 16 +--------------- docker_test.go | 24 ++++++++++++------------ 3 files changed, 24 insertions(+), 38 deletions(-) diff --git a/cleanup.go b/cleanup.go index 7a6564fc15..74d275910c 100644 --- a/cleanup.go +++ b/cleanup.go @@ -11,20 +11,20 @@ import ( // DefaultTimeout for termination var DefaultTimeout = 10 * time.Second -// terminateOptions is a type that holds the options for terminating a container. -type terminateOptions struct { +// TerminateOptions is a type that holds the options for terminating a container. +type TerminateOptions struct { ctx context.Context stopTimeout *time.Duration volumes []string } // TerminateOption is a type that represents an option for terminating a container. -type TerminateOption func(*terminateOptions) +type TerminateOption func(*TerminateOptions) // NewTerminateOptions returns a fully initialised TerminateOptions. // Defaults: StopTimeout: 10 seconds. -func NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *terminateOptions { - options := &terminateOptions{ +func NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *TerminateOptions { + options := &TerminateOptions{ stopTimeout: &DefaultTimeout, ctx: ctx, } @@ -35,17 +35,17 @@ func NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *terminat } // Context returns the context to use duration a Terminate. -func (o *terminateOptions) Context() context.Context { +func (o *TerminateOptions) Context() context.Context { return o.ctx } // StopTimeout returns the stop timeout to use duration a Terminate. -func (o *terminateOptions) StopTimeout() *time.Duration { +func (o *TerminateOptions) StopTimeout() *time.Duration { return o.stopTimeout } // Cleanup performs any clean up needed -func (o *terminateOptions) Cleanup() error { +func (o *TerminateOptions) Cleanup() error { // TODO: simplify this when when perform the client refactor. if len(o.volumes) == 0 { return nil @@ -68,7 +68,7 @@ func (o *terminateOptions) Cleanup() error { // StopContext returns a TerminateOption that sets the context. // Default: context.Background(). func StopContext(ctx context.Context) TerminateOption { - return func(c *terminateOptions) { + return func(c *TerminateOptions) { c.ctx = ctx } } @@ -76,7 +76,7 @@ func StopContext(ctx context.Context) TerminateOption { // StopTimeout returns a TerminateOption that sets the timeout. // Default: See [Container.Stop]. func StopTimeout(timeout time.Duration) TerminateOption { - return func(c *terminateOptions) { + return func(c *TerminateOptions) { c.stopTimeout = &timeout } } @@ -86,7 +86,7 @@ func StopTimeout(timeout time.Duration) TerminateOption { // which are not removed by default. // Default: nil. func RemoveVolumes(volumes ...string) TerminateOption { - return func(c *terminateOptions) { + return func(c *TerminateOptions) { c.volumes = volumes } } diff --git a/docker.go b/docker.go index 398ec7d7fc..2ce849be50 100644 --- a/docker.go +++ b/docker.go @@ -296,20 +296,6 @@ func (c *DockerContainer) Stop(ctx context.Context, timeout *time.Duration) erro return nil } -// WithStopTimeout is a functional option that sets the timeout for the container termination. -func WithStopTimeout(timeout time.Duration) TerminateOption { - return func(c *terminateOptions) { - c.stopTimeout = &timeout - } -} - -// WithTerminateVolumes is a functional option that sets the volumes for the container termination. -func WithTerminateVolumes(volumes ...string) TerminateOption { - return func(opts *terminateOptions) { - opts.volumes = volumes - } -} - // Terminate calls stops and then removes the container including its volumes. // If its image was built it and all child images are also removed unless // the [FromDockerfile.KeepImage] on the [ContainerRequest] was set to true. @@ -356,7 +342,7 @@ func (c *DockerContainer) Terminate(ctx context.Context, opts ...TerminateOption c.sessionID = "" c.isRunning = false - if err := options.Cleanup(); err != nil { + if err = options.Cleanup(); err != nil { errs = append(errs, err) } diff --git a/docker_test.go b/docker_test.go index 8665ad2a00..1a94c7db75 100644 --- a/docker_test.go +++ b/docker_test.go @@ -289,7 +289,7 @@ func TestContainerStateAfterTermination(t *testing.T) { err = nginx.Start(ctx) require.NoError(t, err, "expected no error from container start.") - err = nginx.Terminate(ctx, WithStopTimeout(5*time.Microsecond)) + err = nginx.Terminate(ctx, StopTimeout(5*time.Microsecond)) require.NoError(t, err) }) @@ -1129,34 +1129,34 @@ func TestContainerCreationWithVolumeCleaning(t *testing.T) { Started: true, }) require.NoError(t, err) - err = bashC.Terminate(ctx, WithTerminateVolumes(volumeName)) + err = bashC.Terminate(ctx, RemoveVolumes(volumeName)) CleanupContainer(t, bashC, RemoveVolumes(volumeName)) require.NoError(t, err) } func TestContainerTerminationOptions(t *testing.T) { t.Run("volumes", func(t *testing.T) { - var options terminateOptions - WithTerminateVolumes("vol1", "vol2")(&options) - require.Equal(t, terminateOptions{ + var options TerminateOptions + RemoveVolumes("vol1", "vol2")(&options) + require.Equal(t, TerminateOptions{ volumes: []string{"vol1", "vol2"}, }, options) }) t.Run("stop-timeout", func(t *testing.T) { - var options terminateOptions + var options TerminateOptions timeout := 11 * time.Second - WithStopTimeout(timeout)(&options) - require.Equal(t, terminateOptions{ + StopTimeout(timeout)(&options) + require.Equal(t, TerminateOptions{ stopTimeout: &timeout, }, options) }) t.Run("all", func(t *testing.T) { - var options terminateOptions + var options TerminateOptions timeout := 9 * time.Second - WithStopTimeout(timeout)(&options) - WithTerminateVolumes("vol1", "vol2")(&options) - require.Equal(t, terminateOptions{ + StopTimeout(timeout)(&options) + RemoveVolumes("vol1", "vol2")(&options) + require.Equal(t, TerminateOptions{ stopTimeout: &timeout, volumes: []string{"vol1", "vol2"}, }, options) From 6461173ea56bee064f7523973b309df7144ca977 Mon Sep 17 00:00:00 2001 From: moogacs Date: Sun, 29 Dec 2024 15:47:27 +0100 Subject: [PATCH 08/13] avoid global default --- cleanup.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cleanup.go b/cleanup.go index 74d275910c..da7318fca8 100644 --- a/cleanup.go +++ b/cleanup.go @@ -8,9 +8,6 @@ import ( "time" ) -// DefaultTimeout for termination -var DefaultTimeout = 10 * time.Second - // TerminateOptions is a type that holds the options for terminating a container. type TerminateOptions struct { ctx context.Context @@ -24,8 +21,9 @@ type TerminateOption func(*TerminateOptions) // NewTerminateOptions returns a fully initialised TerminateOptions. // Defaults: StopTimeout: 10 seconds. func NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *TerminateOptions { + timeout := time.Second * 10 options := &TerminateOptions{ - stopTimeout: &DefaultTimeout, + stopTimeout: &timeout, ctx: ctx, } for _, opt := range opts { From ac27a52b7cfcb69815b505065efdc1c1f23c6e15 Mon Sep 17 00:00:00 2001 From: moogacs Date: Tue, 31 Dec 2024 13:20:53 +0100 Subject: [PATCH 09/13] address more comments --- cleanup.go | 4 ++-- docker_test.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cleanup.go b/cleanup.go index da7318fca8..d676b42bdb 100644 --- a/cleanup.go +++ b/cleanup.go @@ -32,12 +32,12 @@ func NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *Terminat return options } -// Context returns the context to use duration a Terminate. +// Context returns the context to use during a Terminate. func (o *TerminateOptions) Context() context.Context { return o.ctx } -// StopTimeout returns the stop timeout to use duration a Terminate. +// StopTimeout returns the stop timeout to use during a Terminate. func (o *TerminateOptions) StopTimeout() *time.Duration { return o.stopTimeout } diff --git a/docker_test.go b/docker_test.go index 1a94c7db75..0dd60f6db9 100644 --- a/docker_test.go +++ b/docker_test.go @@ -1120,6 +1120,7 @@ func TestContainerCreationWithVolumeCleaning(t *testing.T) { { HostFilePath: absPath, ContainerFilePath: "/hello.sh", + FileMode: 700, }, }, Mounts: Mounts(VolumeMount(volumeName, "/data")), From f4c786fe3a10ba1976f9de5df12a6fdc24c1cfe1 Mon Sep 17 00:00:00 2001 From: moogacs Date: Tue, 31 Dec 2024 13:49:42 +0100 Subject: [PATCH 10/13] add docs for new termination options --- docs/features/common_functional_options.md | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/features/common_functional_options.md b/docs/features/common_functional_options.md index 18d0e4b007..23e33c929c 100644 --- a/docs/features/common_functional_options.md +++ b/docs/features/common_functional_options.md @@ -165,3 +165,41 @@ The above example is updating the predefined command of the image, **appending** !!!info This can't be used to replace the command, only to append options. + +#### NewTerminateOptions + +- Since testcontainers-go :material-tag: v0.35.0 + +If you want to attach option to container termination, you can use the `testcontainer.NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *TerminateOptions` option, which receives a TerminateOption as parameter, creating custom termination options to be passed on the container termination. + +##### Terminate Options + +###### [StopContext](../../cleanup.go) +Sets the context for the Container termination. + +- **Function**: `StopContext(ctx context.Context) TerminateOption` +- **Default**: The context passed in `Terminate()` +- **Usage**: +```go +err := container.Terminate(ctx,StopContext(context.Background())) +``` + +###### [StopTimeout](../../cleanup.go) +Sets the timeout for stopping the Container. + +- **Function**: ` StopTimeout(timeout time.Duration) TerminateOption` +- **Default**: 10 seconds +- **Usage**: +```go +err := container.Terminate(ctx, StopTimeout(20 * time.Second)) +``` + +###### [RemoveVolumes](../../cleanup.go) +Sets the volumes to be removed during Container termination. + +- **Function**: ` RemoveVolumes(volumes ...string) TerminateOption` +- **Default**: Empty (no volumes removed) +- **Usage**: +```go +err := container.Terminate(ctx, RemoveVolumes("vol1", "vol2")) +``` From 12a8722001f8ed14adf0b695ee7ad9e5e745da59 Mon Sep 17 00:00:00 2001 From: moogacs Date: Thu, 2 Jan 2025 08:25:30 +0100 Subject: [PATCH 11/13] move docs to garbage_collector/#terminate-function --- docs/features/common_functional_options.md | 38 --------------------- docs/features/garbage_collector.md | 39 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/docs/features/common_functional_options.md b/docs/features/common_functional_options.md index 23e33c929c..18d0e4b007 100644 --- a/docs/features/common_functional_options.md +++ b/docs/features/common_functional_options.md @@ -165,41 +165,3 @@ The above example is updating the predefined command of the image, **appending** !!!info This can't be used to replace the command, only to append options. - -#### NewTerminateOptions - -- Since testcontainers-go :material-tag: v0.35.0 - -If you want to attach option to container termination, you can use the `testcontainer.NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *TerminateOptions` option, which receives a TerminateOption as parameter, creating custom termination options to be passed on the container termination. - -##### Terminate Options - -###### [StopContext](../../cleanup.go) -Sets the context for the Container termination. - -- **Function**: `StopContext(ctx context.Context) TerminateOption` -- **Default**: The context passed in `Terminate()` -- **Usage**: -```go -err := container.Terminate(ctx,StopContext(context.Background())) -``` - -###### [StopTimeout](../../cleanup.go) -Sets the timeout for stopping the Container. - -- **Function**: ` StopTimeout(timeout time.Duration) TerminateOption` -- **Default**: 10 seconds -- **Usage**: -```go -err := container.Terminate(ctx, StopTimeout(20 * time.Second)) -``` - -###### [RemoveVolumes](../../cleanup.go) -Sets the volumes to be removed during Container termination. - -- **Function**: ` RemoveVolumes(volumes ...string) TerminateOption` -- **Default**: Empty (no volumes removed) -- **Usage**: -```go -err := container.Terminate(ctx, RemoveVolumes("vol1", "vol2")) -``` diff --git a/docs/features/garbage_collector.md b/docs/features/garbage_collector.md index e725f5a9bd..aab0e56ce3 100644 --- a/docs/features/garbage_collector.md +++ b/docs/features/garbage_collector.md @@ -17,6 +17,45 @@ The primary method is to use the `Terminate(context.Context)` function that is available when a container is created. Use `defer` to ensure that it is called on test completion. +#### NewTerminateOptions + +- Since testcontainers-go :material-tag: v0.35.0 + +If you want to attach option to container termination, you can use the `testcontainer.NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *TerminateOptions` option, which receives a TerminateOption as parameter, creating custom termination options to be passed on the container termination. + +##### Terminate Options + +###### [StopContext](../../cleanup.go) +Sets the context for the Container termination. + +- **Function**: `StopContext(ctx context.Context) TerminateOption` +- **Default**: The context passed in `Terminate()` +- **Usage**: +```go +err := container.Terminate(ctx,StopContext(context.Background())) +``` + +###### [StopTimeout](../../cleanup.go) +Sets the timeout for stopping the Container. + +- **Function**: ` StopTimeout(timeout time.Duration) TerminateOption` +- **Default**: 10 seconds +- **Usage**: +```go +err := container.Terminate(ctx, StopTimeout(20 * time.Second)) +``` + +###### [RemoveVolumes](../../cleanup.go) +Sets the volumes to be removed during Container termination. + +- **Function**: ` RemoveVolumes(volumes ...string) TerminateOption` +- **Default**: Empty (no volumes removed) +- **Usage**: +```go +err := container.Terminate(ctx, RemoveVolumes("vol1", "vol2")) +``` + + !!!tip Remember to `defer` as soon as possible so you won't forget. The best time From f6d6fd259e7007c1e7e7b96e7b7a5fd5f1924de1 Mon Sep 17 00:00:00 2001 From: moogacs Date: Thu, 2 Jan 2025 09:01:16 +0100 Subject: [PATCH 12/13] resolve doc comments --- docs/features/garbage_collector.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/features/garbage_collector.md b/docs/features/garbage_collector.md index aab0e56ce3..4d0a1e08b2 100644 --- a/docs/features/garbage_collector.md +++ b/docs/features/garbage_collector.md @@ -17,9 +17,11 @@ The primary method is to use the `Terminate(context.Context)` function that is available when a container is created. Use `defer` to ensure that it is called on test completion. +The `Terminate` function can be customised with termination options to determine how a container is removed: termination timeout, and the ability to remove container volumes are supported at the moment. You can build the default options using the `testcontainers.NewTerminationOptions` function. + #### NewTerminateOptions -- Since testcontainers-go :material-tag: v0.35.0 +- Not available until the next release of testcontainers-go :material-tag: main If you want to attach option to container termination, you can use the `testcontainer.NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *TerminateOptions` option, which receives a TerminateOption as parameter, creating custom termination options to be passed on the container termination. From 4ccbbb1fa02f895e24515ca7f65a5ea328a7832e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 2 Jan 2025 09:58:47 +0100 Subject: [PATCH 13/13] docs: typo --- docs/features/garbage_collector.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/garbage_collector.md b/docs/features/garbage_collector.md index 4d0a1e08b2..4712c59748 100644 --- a/docs/features/garbage_collector.md +++ b/docs/features/garbage_collector.md @@ -23,7 +23,7 @@ The `Terminate` function can be customised with termination options to determine - Not available until the next release of testcontainers-go :material-tag: main -If you want to attach option to container termination, you can use the `testcontainer.NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *TerminateOptions` option, which receives a TerminateOption as parameter, creating custom termination options to be passed on the container termination. +If you want to attach option to container termination, you can use the `testcontainers.NewTerminateOptions(ctx context.Context, opts ...TerminateOption) *TerminateOptions` option, which receives a TerminateOption as parameter, creating custom termination options to be passed on the container termination. ##### Terminate Options