Files
query-orchestration/vendor/github.com/testcontainers/testcontainers-go/cleanup.go
T
2024-12-19 11:44:13 +00:00

108 lines
2.7 KiB
Go

package testcontainers
import (
"context"
"errors"
"fmt"
"reflect"
"time"
)
// terminateOptions is a type that holds the options for terminating a container.
type terminateOptions struct {
ctx context.Context
timeout *time.Duration
volumes []string
}
// TerminateOption is a type that represents an option for terminating a container.
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
}
}
// 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
}
}
// RemoveVolumes returns a TerminateOption that sets additional volumes to remove.
// This is useful when the container creates named volumes that should be removed
// which are not removed by default.
// Default: nil.
func RemoveVolumes(volumes ...string) TerminateOption {
return func(c *terminateOptions) {
c.volumes = volumes
}
}
// TerminateContainer calls [Container.Terminate] on the container if it is not nil.
//
// This should be called as a defer directly after [GenericContainer](...)
// or a modules Run(...) to ensure the container is terminated when the
// function ends.
func TerminateContainer(container Container, options ...TerminateOption) error {
if isNil(container) {
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)
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...)
}
// isNil returns true if val is nil or an nil instance false otherwise.
func isNil(val any) bool {
if val == nil {
return true
}
valueOf := reflect.ValueOf(val)
switch valueOf.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
return valueOf.IsNil()
default:
return false
}
}