b1f8ac453b
feat!: base template * first bit of templating * codeowners * linuxbased * restart * baseline project * add grpc api and basic integration test * startqueue * queueMsg * splitscripts * migrations * queueintegrationtest * gateway
108 lines
2.6 KiB
Go
108 lines
2.6 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
|
|
}
|
|
}
|