summaryrefslogtreecommitdiff
path: root/vendor/github.com/testcontainers/testcontainers-go/wait
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-05-11 21:12:57 -0600
committermo khan <mo@mokhan.ca>2025-05-11 21:12:57 -0600
commit60440f90dca28e99a31dd328c5f6d5dc0f9b6a2e (patch)
tree2f54adf55086516f162f0a55a5347e6b25f7f176 /vendor/github.com/testcontainers/testcontainers-go/wait
parent05ca9b8d3a9c7203a3a3b590beaa400900bd9007 (diff)
chore: vendor go dependencies
Diffstat (limited to 'vendor/github.com/testcontainers/testcontainers-go/wait')
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/all.go82
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/errors.go13
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/errors_windows.go9
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/exec.go107
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/exit.go89
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/file.go112
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/health.go92
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/host_port.go245
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/http.go338
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/log.go214
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/nop.go81
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/sql.go118
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/tls.go167
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/wait.go65
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/wait/walk.go74
15 files changed, 1806 insertions, 0 deletions
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/all.go b/vendor/github.com/testcontainers/testcontainers-go/wait/all.go
new file mode 100644
index 0000000..fb7eb4e
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/all.go
@@ -0,0 +1,82 @@
+package wait
+
+import (
+ "context"
+ "errors"
+ "time"
+)
+
+// Implement interface
+var (
+ _ Strategy = (*MultiStrategy)(nil)
+ _ StrategyTimeout = (*MultiStrategy)(nil)
+)
+
+type MultiStrategy struct {
+ // all Strategies should have a startupTimeout to avoid waiting infinitely
+ timeout *time.Duration
+ deadline *time.Duration
+
+ // additional properties
+ Strategies []Strategy
+}
+
+// WithStartupTimeoutDefault sets the default timeout for all inner wait strategies
+func (ms *MultiStrategy) WithStartupTimeoutDefault(timeout time.Duration) *MultiStrategy {
+ ms.timeout = &timeout
+ return ms
+}
+
+// WithStartupTimeout sets a time.Duration which limits all wait strategies
+//
+// Deprecated: use WithDeadline
+func (ms *MultiStrategy) WithStartupTimeout(timeout time.Duration) Strategy {
+ return ms.WithDeadline(timeout)
+}
+
+// WithDeadline sets a time.Duration which limits all wait strategies
+func (ms *MultiStrategy) WithDeadline(deadline time.Duration) *MultiStrategy {
+ ms.deadline = &deadline
+ return ms
+}
+
+func ForAll(strategies ...Strategy) *MultiStrategy {
+ return &MultiStrategy{
+ Strategies: strategies,
+ }
+}
+
+func (ms *MultiStrategy) Timeout() *time.Duration {
+ return ms.timeout
+}
+
+func (ms *MultiStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) error {
+ var cancel context.CancelFunc
+ if ms.deadline != nil {
+ ctx, cancel = context.WithTimeout(ctx, *ms.deadline)
+ defer cancel()
+ }
+
+ if len(ms.Strategies) == 0 {
+ return errors.New("no wait strategy supplied")
+ }
+
+ for _, strategy := range ms.Strategies {
+ strategyCtx := ctx
+
+ // Set default Timeout when strategy implements StrategyTimeout
+ if st, ok := strategy.(StrategyTimeout); ok {
+ if ms.Timeout() != nil && st.Timeout() == nil {
+ strategyCtx, cancel = context.WithTimeout(ctx, *ms.Timeout())
+ defer cancel()
+ }
+ }
+
+ err := strategy.WaitUntilReady(strategyCtx, target)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/errors.go b/vendor/github.com/testcontainers/testcontainers-go/wait/errors.go
new file mode 100644
index 0000000..3e3919a
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/errors.go
@@ -0,0 +1,13 @@
+//go:build !windows
+// +build !windows
+
+package wait
+
+import (
+ "errors"
+ "syscall"
+)
+
+func isConnRefusedErr(err error) bool {
+ return errors.Is(err, syscall.ECONNREFUSED)
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/errors_windows.go b/vendor/github.com/testcontainers/testcontainers-go/wait/errors_windows.go
new file mode 100644
index 0000000..3ae346d
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/errors_windows.go
@@ -0,0 +1,9 @@
+package wait
+
+import (
+ "golang.org/x/sys/windows"
+)
+
+func isConnRefusedErr(err error) bool {
+ return err == windows.WSAECONNREFUSED
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/exec.go b/vendor/github.com/testcontainers/testcontainers-go/wait/exec.go
new file mode 100644
index 0000000..72987c3
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/exec.go
@@ -0,0 +1,107 @@
+package wait
+
+import (
+ "context"
+ "io"
+ "time"
+
+ tcexec "github.com/testcontainers/testcontainers-go/exec"
+)
+
+// Implement interface
+var (
+ _ Strategy = (*ExecStrategy)(nil)
+ _ StrategyTimeout = (*ExecStrategy)(nil)
+)
+
+type ExecStrategy struct {
+ // all Strategies should have a startupTimeout to avoid waiting infinitely
+ timeout *time.Duration
+ cmd []string
+
+ // additional properties
+ ExitCodeMatcher func(exitCode int) bool
+ ResponseMatcher func(body io.Reader) bool
+ PollInterval time.Duration
+}
+
+// NewExecStrategy constructs an Exec strategy ...
+func NewExecStrategy(cmd []string) *ExecStrategy {
+ return &ExecStrategy{
+ cmd: cmd,
+ ExitCodeMatcher: defaultExitCodeMatcher,
+ ResponseMatcher: func(_ io.Reader) bool { return true },
+ PollInterval: defaultPollInterval(),
+ }
+}
+
+func defaultExitCodeMatcher(exitCode int) bool {
+ return exitCode == 0
+}
+
+// WithStartupTimeout can be used to change the default startup timeout
+func (ws *ExecStrategy) WithStartupTimeout(startupTimeout time.Duration) *ExecStrategy {
+ ws.timeout = &startupTimeout
+ return ws
+}
+
+func (ws *ExecStrategy) WithExitCode(exitCode int) *ExecStrategy {
+ return ws.WithExitCodeMatcher(func(actualCode int) bool {
+ return actualCode == exitCode
+ })
+}
+
+func (ws *ExecStrategy) WithExitCodeMatcher(exitCodeMatcher func(exitCode int) bool) *ExecStrategy {
+ ws.ExitCodeMatcher = exitCodeMatcher
+ return ws
+}
+
+func (ws *ExecStrategy) WithResponseMatcher(matcher func(body io.Reader) bool) *ExecStrategy {
+ ws.ResponseMatcher = matcher
+ return ws
+}
+
+// WithPollInterval can be used to override the default polling interval of 100 milliseconds
+func (ws *ExecStrategy) WithPollInterval(pollInterval time.Duration) *ExecStrategy {
+ ws.PollInterval = pollInterval
+ return ws
+}
+
+// ForExec is a convenience method to assign ExecStrategy
+func ForExec(cmd []string) *ExecStrategy {
+ return NewExecStrategy(cmd)
+}
+
+func (ws *ExecStrategy) Timeout() *time.Duration {
+ return ws.timeout
+}
+
+func (ws *ExecStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) error {
+ timeout := defaultStartupTimeout()
+ if ws.timeout != nil {
+ timeout = *ws.timeout
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, timeout)
+ defer cancel()
+
+ for {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-time.After(ws.PollInterval):
+ exitCode, resp, err := target.Exec(ctx, ws.cmd, tcexec.Multiplexed())
+ if err != nil {
+ return err
+ }
+ if !ws.ExitCodeMatcher(exitCode) {
+ continue
+ }
+ if ws.ResponseMatcher != nil && !ws.ResponseMatcher(resp) {
+ continue
+ }
+
+ return nil
+ }
+ }
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/exit.go b/vendor/github.com/testcontainers/testcontainers-go/wait/exit.go
new file mode 100644
index 0000000..670c8e2
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/exit.go
@@ -0,0 +1,89 @@
+package wait
+
+import (
+ "context"
+ "strings"
+ "time"
+)
+
+// Implement interface
+var (
+ _ Strategy = (*ExitStrategy)(nil)
+ _ StrategyTimeout = (*ExitStrategy)(nil)
+)
+
+// ExitStrategy will wait until container exit
+type ExitStrategy struct {
+ // all Strategies should have a timeout to avoid waiting infinitely
+ timeout *time.Duration
+
+ // additional properties
+ PollInterval time.Duration
+}
+
+// NewExitStrategy constructs with polling interval of 100 milliseconds without timeout by default
+func NewExitStrategy() *ExitStrategy {
+ return &ExitStrategy{
+ PollInterval: defaultPollInterval(),
+ }
+}
+
+// fluent builders for each property
+// since go has neither covariance nor generics, the return type must be the type of the concrete implementation
+// this is true for all properties, even the "shared" ones
+
+// WithExitTimeout can be used to change the default exit timeout
+func (ws *ExitStrategy) WithExitTimeout(exitTimeout time.Duration) *ExitStrategy {
+ ws.timeout = &exitTimeout
+ return ws
+}
+
+// WithPollInterval can be used to override the default polling interval of 100 milliseconds
+func (ws *ExitStrategy) WithPollInterval(pollInterval time.Duration) *ExitStrategy {
+ ws.PollInterval = pollInterval
+ return ws
+}
+
+// ForExit is the default construction for the fluid interface.
+//
+// For Example:
+//
+// wait.
+// ForExit().
+// WithPollInterval(1 * time.Second)
+func ForExit() *ExitStrategy {
+ return NewExitStrategy()
+}
+
+func (ws *ExitStrategy) Timeout() *time.Duration {
+ return ws.timeout
+}
+
+// WaitUntilReady implements Strategy.WaitUntilReady
+func (ws *ExitStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) error {
+ if ws.timeout != nil {
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithTimeout(ctx, *ws.timeout)
+ defer cancel()
+ }
+
+ for {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ state, err := target.State(ctx)
+ if err != nil {
+ if !strings.Contains(err.Error(), "No such container") {
+ return err
+ }
+ return nil
+ }
+ if state.Running {
+ time.Sleep(ws.PollInterval)
+ continue
+ }
+ return nil
+ }
+ }
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/file.go b/vendor/github.com/testcontainers/testcontainers-go/wait/file.go
new file mode 100644
index 0000000..d9cab7a
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/file.go
@@ -0,0 +1,112 @@
+package wait
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "time"
+
+ "github.com/docker/docker/errdefs"
+)
+
+var (
+ _ Strategy = (*FileStrategy)(nil)
+ _ StrategyTimeout = (*FileStrategy)(nil)
+)
+
+// FileStrategy waits for a file to exist in the container.
+type FileStrategy struct {
+ timeout *time.Duration
+ file string
+ pollInterval time.Duration
+ matcher func(io.Reader) error
+}
+
+// NewFileStrategy constructs an FileStrategy strategy.
+func NewFileStrategy(file string) *FileStrategy {
+ return &FileStrategy{
+ file: file,
+ pollInterval: defaultPollInterval(),
+ }
+}
+
+// WithStartupTimeout can be used to change the default startup timeout
+func (ws *FileStrategy) WithStartupTimeout(startupTimeout time.Duration) *FileStrategy {
+ ws.timeout = &startupTimeout
+ return ws
+}
+
+// WithPollInterval can be used to override the default polling interval of 100 milliseconds
+func (ws *FileStrategy) WithPollInterval(pollInterval time.Duration) *FileStrategy {
+ ws.pollInterval = pollInterval
+ return ws
+}
+
+// WithMatcher can be used to consume the file content.
+// The matcher can return an errdefs.ErrNotFound to indicate that the file is not ready.
+// Any other error will be considered a failure.
+// Default: nil, will only wait for the file to exist.
+func (ws *FileStrategy) WithMatcher(matcher func(io.Reader) error) *FileStrategy {
+ ws.matcher = matcher
+ return ws
+}
+
+// ForFile is a convenience method to assign FileStrategy
+func ForFile(file string) *FileStrategy {
+ return NewFileStrategy(file)
+}
+
+// Timeout returns the timeout for the strategy
+func (ws *FileStrategy) Timeout() *time.Duration {
+ return ws.timeout
+}
+
+// WaitUntilReady waits until the file exists in the container and copies it to the target.
+func (ws *FileStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) error {
+ timeout := defaultStartupTimeout()
+ if ws.timeout != nil {
+ timeout = *ws.timeout
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, timeout)
+ defer cancel()
+
+ timer := time.NewTicker(ws.pollInterval)
+ defer timer.Stop()
+ for {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-timer.C:
+ if err := ws.matchFile(ctx, target); err != nil {
+ if errdefs.IsNotFound(err) {
+ // Not found, continue polling.
+ continue
+ }
+
+ return fmt.Errorf("copy from container: %w", err)
+ }
+ return nil
+ }
+ }
+}
+
+// matchFile tries to copy the file from the container and match it.
+func (ws *FileStrategy) matchFile(ctx context.Context, target StrategyTarget) error {
+ rc, err := target.CopyFileFromContainer(ctx, ws.file)
+ if err != nil {
+ return fmt.Errorf("copy from container: %w", err)
+ }
+ defer rc.Close()
+
+ if ws.matcher == nil {
+ // No matcher, just check if the file exists.
+ return nil
+ }
+
+ if err = ws.matcher(rc); err != nil {
+ return fmt.Errorf("matcher: %w", err)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/health.go b/vendor/github.com/testcontainers/testcontainers-go/wait/health.go
new file mode 100644
index 0000000..06a9ad1
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/health.go
@@ -0,0 +1,92 @@
+package wait
+
+import (
+ "context"
+ "time"
+
+ "github.com/docker/docker/api/types"
+)
+
+// Implement interface
+var (
+ _ Strategy = (*HealthStrategy)(nil)
+ _ StrategyTimeout = (*HealthStrategy)(nil)
+)
+
+// HealthStrategy will wait until the container becomes healthy
+type HealthStrategy struct {
+ // all Strategies should have a startupTimeout to avoid waiting infinitely
+ timeout *time.Duration
+
+ // additional properties
+ PollInterval time.Duration
+}
+
+// NewHealthStrategy constructs with polling interval of 100 milliseconds and startup timeout of 60 seconds by default
+func NewHealthStrategy() *HealthStrategy {
+ return &HealthStrategy{
+ PollInterval: defaultPollInterval(),
+ }
+}
+
+// fluent builders for each property
+// since go has neither covariance nor generics, the return type must be the type of the concrete implementation
+// this is true for all properties, even the "shared" ones like startupTimeout
+
+// WithStartupTimeout can be used to change the default startup timeout
+func (ws *HealthStrategy) WithStartupTimeout(startupTimeout time.Duration) *HealthStrategy {
+ ws.timeout = &startupTimeout
+ return ws
+}
+
+// WithPollInterval can be used to override the default polling interval of 100 milliseconds
+func (ws *HealthStrategy) WithPollInterval(pollInterval time.Duration) *HealthStrategy {
+ ws.PollInterval = pollInterval
+ return ws
+}
+
+// ForHealthCheck is the default construction for the fluid interface.
+//
+// For Example:
+//
+// wait.
+// ForHealthCheck().
+// WithPollInterval(1 * time.Second)
+func ForHealthCheck() *HealthStrategy {
+ return NewHealthStrategy()
+}
+
+func (ws *HealthStrategy) Timeout() *time.Duration {
+ return ws.timeout
+}
+
+// WaitUntilReady implements Strategy.WaitUntilReady
+func (ws *HealthStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) error {
+ timeout := defaultStartupTimeout()
+ if ws.timeout != nil {
+ timeout = *ws.timeout
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, timeout)
+ defer cancel()
+
+ for {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ state, err := target.State(ctx)
+ if err != nil {
+ return err
+ }
+ if err := checkState(state); err != nil {
+ return err
+ }
+ if state.Health == nil || state.Health.Status != types.Healthy {
+ time.Sleep(ws.PollInterval)
+ continue
+ }
+ return nil
+ }
+ }
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/host_port.go b/vendor/github.com/testcontainers/testcontainers-go/wait/host_port.go
new file mode 100644
index 0000000..2070bf1
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/host_port.go
@@ -0,0 +1,245 @@
+package wait
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net"
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/docker/go-connections/nat"
+
+ "github.com/testcontainers/testcontainers-go/log"
+)
+
+const (
+ exitEaccess = 126 // container cmd can't be invoked (permission denied)
+ exitCmdNotFound = 127 // container cmd not found/does not exist or invalid bind-mount
+)
+
+// Implement interface
+var (
+ _ Strategy = (*HostPortStrategy)(nil)
+ _ StrategyTimeout = (*HostPortStrategy)(nil)
+)
+
+var (
+ errShellNotExecutable = errors.New("/bin/sh command not executable")
+ errShellNotFound = errors.New("/bin/sh command not found")
+)
+
+type HostPortStrategy struct {
+ // Port is a string containing port number and protocol in the format "80/tcp"
+ // which
+ Port nat.Port
+ // all WaitStrategies should have a startupTimeout to avoid waiting infinitely
+ timeout *time.Duration
+ PollInterval time.Duration
+
+ // skipInternalCheck is a flag to skip the internal check, which is useful when
+ // a shell is not available in the container or when the container doesn't bind
+ // the port internally until additional conditions are met.
+ skipInternalCheck bool
+}
+
+// NewHostPortStrategy constructs a default host port strategy that waits for the given
+// port to be exposed. The default startup timeout is 60 seconds.
+func NewHostPortStrategy(port nat.Port) *HostPortStrategy {
+ return &HostPortStrategy{
+ Port: port,
+ PollInterval: defaultPollInterval(),
+ }
+}
+
+// fluent builders for each property
+// since go has neither covariance nor generics, the return type must be the type of the concrete implementation
+// this is true for all properties, even the "shared" ones like startupTimeout
+
+// ForListeningPort returns a host port strategy that waits for the given port
+// to be exposed and bound internally the container.
+// Alias for `NewHostPortStrategy(port)`.
+func ForListeningPort(port nat.Port) *HostPortStrategy {
+ return NewHostPortStrategy(port)
+}
+
+// ForExposedPort returns a host port strategy that waits for the first port
+// to be exposed and bound internally the container.
+func ForExposedPort() *HostPortStrategy {
+ return NewHostPortStrategy("")
+}
+
+// SkipInternalCheck changes the host port strategy to skip the internal check,
+// which is useful when a shell is not available in the container or when the
+// container doesn't bind the port internally until additional conditions are met.
+func (hp *HostPortStrategy) SkipInternalCheck() *HostPortStrategy {
+ hp.skipInternalCheck = true
+
+ return hp
+}
+
+// WithStartupTimeout can be used to change the default startup timeout
+func (hp *HostPortStrategy) WithStartupTimeout(startupTimeout time.Duration) *HostPortStrategy {
+ hp.timeout = &startupTimeout
+ return hp
+}
+
+// WithPollInterval can be used to override the default polling interval of 100 milliseconds
+func (hp *HostPortStrategy) WithPollInterval(pollInterval time.Duration) *HostPortStrategy {
+ hp.PollInterval = pollInterval
+ return hp
+}
+
+func (hp *HostPortStrategy) Timeout() *time.Duration {
+ return hp.timeout
+}
+
+// WaitUntilReady implements Strategy.WaitUntilReady
+func (hp *HostPortStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) error {
+ timeout := defaultStartupTimeout()
+ if hp.timeout != nil {
+ timeout = *hp.timeout
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, timeout)
+ defer cancel()
+
+ ipAddress, err := target.Host(ctx)
+ if err != nil {
+ return err
+ }
+
+ waitInterval := hp.PollInterval
+
+ internalPort := hp.Port
+ if internalPort == "" {
+ inspect, err := target.Inspect(ctx)
+ if err != nil {
+ return err
+ }
+
+ for port := range inspect.NetworkSettings.Ports {
+ if internalPort == "" || port.Int() < internalPort.Int() {
+ internalPort = port
+ }
+ }
+ }
+
+ if internalPort == "" {
+ return errors.New("no port to wait for")
+ }
+
+ var port nat.Port
+ port, err = target.MappedPort(ctx, internalPort)
+ i := 0
+
+ for port == "" {
+ i++
+
+ select {
+ case <-ctx.Done():
+ return fmt.Errorf("mapped port: retries: %d, port: %q, last err: %w, ctx err: %w", i, port, err, ctx.Err())
+ case <-time.After(waitInterval):
+ if err := checkTarget(ctx, target); err != nil {
+ return fmt.Errorf("check target: retries: %d, port: %q, last err: %w", i, port, err)
+ }
+ port, err = target.MappedPort(ctx, internalPort)
+ if err != nil {
+ log.Printf("mapped port: retries: %d, port: %q, err: %s\n", i, port, err)
+ }
+ }
+ }
+
+ if err := externalCheck(ctx, ipAddress, port, target, waitInterval); err != nil {
+ return fmt.Errorf("external check: %w", err)
+ }
+
+ if hp.skipInternalCheck {
+ return nil
+ }
+
+ if err = internalCheck(ctx, internalPort, target); err != nil {
+ switch {
+ case errors.Is(err, errShellNotExecutable):
+ log.Printf("Shell not executable in container, only external port validated")
+ return nil
+ case errors.Is(err, errShellNotFound):
+ log.Printf("Shell not found in container")
+ return nil
+ default:
+ return fmt.Errorf("internal check: %w", err)
+ }
+ }
+
+ return nil
+}
+
+func externalCheck(ctx context.Context, ipAddress string, port nat.Port, target StrategyTarget, waitInterval time.Duration) error {
+ proto := port.Proto()
+ portNumber := port.Int()
+ portString := strconv.Itoa(portNumber)
+
+ dialer := net.Dialer{}
+ address := net.JoinHostPort(ipAddress, portString)
+ for i := 0; ; i++ {
+ if err := checkTarget(ctx, target); err != nil {
+ return fmt.Errorf("check target: retries: %d address: %s: %w", i, address, err)
+ }
+ conn, err := dialer.DialContext(ctx, proto, address)
+ if err != nil {
+ var v *net.OpError
+ if errors.As(err, &v) {
+ var v2 *os.SyscallError
+ if errors.As(v.Err, &v2) {
+ if isConnRefusedErr(v2.Err) {
+ time.Sleep(waitInterval)
+ continue
+ }
+ }
+ }
+ return fmt.Errorf("dial: %w", err)
+ }
+
+ conn.Close()
+ return nil
+ }
+}
+
+func internalCheck(ctx context.Context, internalPort nat.Port, target StrategyTarget) error {
+ command := buildInternalCheckCommand(internalPort.Int())
+ for {
+ if ctx.Err() != nil {
+ return ctx.Err()
+ }
+ if err := checkTarget(ctx, target); err != nil {
+ return err
+ }
+ exitCode, _, err := target.Exec(ctx, []string{"/bin/sh", "-c", command})
+ if err != nil {
+ return fmt.Errorf("%w, host port waiting failed", err)
+ }
+
+ // Docker has an issue which override exit code 127 to 126 due to:
+ // https://github.com/moby/moby/issues/45795
+ // Handle both to ensure compatibility with Docker and Podman for now.
+ switch exitCode {
+ case 0:
+ return nil
+ case exitEaccess:
+ return errShellNotExecutable
+ case exitCmdNotFound:
+ return errShellNotFound
+ }
+ }
+}
+
+func buildInternalCheckCommand(internalPort int) string {
+ command := `(
+ cat /proc/net/tcp* | awk '{print $2}' | grep -i :%04x ||
+ nc -vz -w 1 localhost %d ||
+ /bin/sh -c '</dev/tcp/localhost/%d'
+ )
+ `
+ return "true && " + fmt.Sprintf(command, internalPort, internalPort, internalPort)
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/http.go b/vendor/github.com/testcontainers/testcontainers-go/wait/http.go
new file mode 100644
index 0000000..2c7c655
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/http.go
@@ -0,0 +1,338 @@
+package wait
+
+import (
+ "bytes"
+ "context"
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/docker/go-connections/nat"
+)
+
+// Implement interface
+var (
+ _ Strategy = (*HTTPStrategy)(nil)
+ _ StrategyTimeout = (*HTTPStrategy)(nil)
+)
+
+type HTTPStrategy struct {
+ // all Strategies should have a startupTimeout to avoid waiting infinitely
+ timeout *time.Duration
+
+ // additional properties
+ Port nat.Port
+ Path string
+ StatusCodeMatcher func(status int) bool
+ ResponseMatcher func(body io.Reader) bool
+ UseTLS bool
+ AllowInsecure bool
+ TLSConfig *tls.Config // TLS config for HTTPS
+ Method string // http method
+ Body io.Reader // http request body
+ Headers map[string]string
+ ResponseHeadersMatcher func(headers http.Header) bool
+ PollInterval time.Duration
+ UserInfo *url.Userinfo
+ ForceIPv4LocalHost bool
+}
+
+// NewHTTPStrategy constructs a HTTP strategy waiting on port 80 and status code 200
+func NewHTTPStrategy(path string) *HTTPStrategy {
+ return &HTTPStrategy{
+ Port: "",
+ Path: path,
+ StatusCodeMatcher: defaultStatusCodeMatcher,
+ ResponseMatcher: func(_ io.Reader) bool { return true },
+ UseTLS: false,
+ TLSConfig: nil,
+ Method: http.MethodGet,
+ Body: nil,
+ Headers: map[string]string{},
+ ResponseHeadersMatcher: func(_ http.Header) bool { return true },
+ PollInterval: defaultPollInterval(),
+ UserInfo: nil,
+ }
+}
+
+func defaultStatusCodeMatcher(status int) bool {
+ return status == http.StatusOK
+}
+
+// fluent builders for each property
+// since go has neither covariance nor generics, the return type must be the type of the concrete implementation
+// this is true for all properties, even the "shared" ones like startupTimeout
+
+// WithStartupTimeout can be used to change the default startup timeout
+func (ws *HTTPStrategy) WithStartupTimeout(timeout time.Duration) *HTTPStrategy {
+ ws.timeout = &timeout
+ return ws
+}
+
+// WithPort set the port to wait for.
+// Default is the lowest numbered port.
+func (ws *HTTPStrategy) WithPort(port nat.Port) *HTTPStrategy {
+ ws.Port = port
+ return ws
+}
+
+func (ws *HTTPStrategy) WithStatusCodeMatcher(statusCodeMatcher func(status int) bool) *HTTPStrategy {
+ ws.StatusCodeMatcher = statusCodeMatcher
+ return ws
+}
+
+func (ws *HTTPStrategy) WithResponseMatcher(matcher func(body io.Reader) bool) *HTTPStrategy {
+ ws.ResponseMatcher = matcher
+ return ws
+}
+
+func (ws *HTTPStrategy) WithTLS(useTLS bool, tlsconf ...*tls.Config) *HTTPStrategy {
+ ws.UseTLS = useTLS
+ if useTLS && len(tlsconf) > 0 {
+ ws.TLSConfig = tlsconf[0]
+ }
+ return ws
+}
+
+func (ws *HTTPStrategy) WithAllowInsecure(allowInsecure bool) *HTTPStrategy {
+ ws.AllowInsecure = allowInsecure
+ return ws
+}
+
+func (ws *HTTPStrategy) WithMethod(method string) *HTTPStrategy {
+ ws.Method = method
+ return ws
+}
+
+func (ws *HTTPStrategy) WithBody(reqdata io.Reader) *HTTPStrategy {
+ ws.Body = reqdata
+ return ws
+}
+
+func (ws *HTTPStrategy) WithHeaders(headers map[string]string) *HTTPStrategy {
+ ws.Headers = headers
+ return ws
+}
+
+func (ws *HTTPStrategy) WithResponseHeadersMatcher(matcher func(http.Header) bool) *HTTPStrategy {
+ ws.ResponseHeadersMatcher = matcher
+ return ws
+}
+
+func (ws *HTTPStrategy) WithBasicAuth(username, password string) *HTTPStrategy {
+ ws.UserInfo = url.UserPassword(username, password)
+ return ws
+}
+
+// WithPollInterval can be used to override the default polling interval of 100 milliseconds
+func (ws *HTTPStrategy) WithPollInterval(pollInterval time.Duration) *HTTPStrategy {
+ ws.PollInterval = pollInterval
+ return ws
+}
+
+// WithForcedIPv4LocalHost forces usage of localhost to be ipv4 127.0.0.1
+// to avoid ipv6 docker bugs https://github.com/moby/moby/issues/42442 https://github.com/moby/moby/issues/42375
+func (ws *HTTPStrategy) WithForcedIPv4LocalHost() *HTTPStrategy {
+ ws.ForceIPv4LocalHost = true
+ return ws
+}
+
+// ForHTTP is a convenience method similar to Wait.java
+// https://github.com/testcontainers/testcontainers-java/blob/1d85a3834bd937f80aad3a4cec249c027f31aeb4/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java
+func ForHTTP(path string) *HTTPStrategy {
+ return NewHTTPStrategy(path)
+}
+
+func (ws *HTTPStrategy) Timeout() *time.Duration {
+ return ws.timeout
+}
+
+// WaitUntilReady implements Strategy.WaitUntilReady
+func (ws *HTTPStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) error {
+ timeout := defaultStartupTimeout()
+ if ws.timeout != nil {
+ timeout = *ws.timeout
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, timeout)
+ defer cancel()
+
+ ipAddress, err := target.Host(ctx)
+ if err != nil {
+ return err
+ }
+ // to avoid ipv6 docker bugs https://github.com/moby/moby/issues/42442 https://github.com/moby/moby/issues/42375
+ if ws.ForceIPv4LocalHost {
+ ipAddress = strings.Replace(ipAddress, "localhost", "127.0.0.1", 1)
+ }
+
+ var mappedPort nat.Port
+ if ws.Port == "" {
+ // We wait one polling interval before we grab the ports
+ // otherwise they might not be bound yet on startup.
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-time.After(ws.PollInterval):
+ // Port should now be bound so just continue.
+ }
+
+ if err := checkTarget(ctx, target); err != nil {
+ return err
+ }
+
+ inspect, err := target.Inspect(ctx)
+ if err != nil {
+ return err
+ }
+
+ // Find the lowest numbered exposed tcp port.
+ var lowestPort nat.Port
+ var hostPort string
+ for port, bindings := range inspect.NetworkSettings.Ports {
+ if len(bindings) == 0 || port.Proto() != "tcp" {
+ continue
+ }
+
+ if lowestPort == "" || port.Int() < lowestPort.Int() {
+ lowestPort = port
+ hostPort = bindings[0].HostPort
+ }
+ }
+
+ if lowestPort == "" {
+ return errors.New("No exposed tcp ports or mapped ports - cannot wait for status")
+ }
+
+ mappedPort, _ = nat.NewPort(lowestPort.Proto(), hostPort)
+ } else {
+ mappedPort, err = target.MappedPort(ctx, ws.Port)
+
+ for mappedPort == "" {
+ select {
+ case <-ctx.Done():
+ return fmt.Errorf("%w: %w", ctx.Err(), err)
+ case <-time.After(ws.PollInterval):
+ if err := checkTarget(ctx, target); err != nil {
+ return err
+ }
+
+ mappedPort, err = target.MappedPort(ctx, ws.Port)
+ }
+ }
+
+ if mappedPort.Proto() != "tcp" {
+ return errors.New("Cannot use HTTP client on non-TCP ports")
+ }
+ }
+
+ switch ws.Method {
+ case http.MethodGet, http.MethodHead, http.MethodPost,
+ http.MethodPut, http.MethodPatch, http.MethodDelete,
+ http.MethodConnect, http.MethodOptions, http.MethodTrace:
+ default:
+ if ws.Method != "" {
+ return fmt.Errorf("invalid http method %q", ws.Method)
+ }
+ ws.Method = http.MethodGet
+ }
+
+ tripper := &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ DialContext: (&net.Dialer{
+ Timeout: time.Second,
+ KeepAlive: 30 * time.Second,
+ DualStack: true,
+ }).DialContext,
+ ForceAttemptHTTP2: true,
+ MaxIdleConns: 100,
+ IdleConnTimeout: 90 * time.Second,
+ TLSHandshakeTimeout: 10 * time.Second,
+ ExpectContinueTimeout: 1 * time.Second,
+ TLSClientConfig: ws.TLSConfig,
+ }
+
+ var proto string
+ if ws.UseTLS {
+ proto = "https"
+ if ws.AllowInsecure {
+ if ws.TLSConfig == nil {
+ tripper.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+ } else {
+ ws.TLSConfig.InsecureSkipVerify = true
+ }
+ }
+ } else {
+ proto = "http"
+ }
+
+ client := http.Client{Transport: tripper, Timeout: time.Second}
+ address := net.JoinHostPort(ipAddress, strconv.Itoa(mappedPort.Int()))
+
+ endpoint, err := url.Parse(ws.Path)
+ if err != nil {
+ return err
+ }
+ endpoint.Scheme = proto
+ endpoint.Host = address
+
+ if ws.UserInfo != nil {
+ endpoint.User = ws.UserInfo
+ }
+
+ // cache the body into a byte-slice so that it can be iterated over multiple times
+ var body []byte
+ if ws.Body != nil {
+ body, err = io.ReadAll(ws.Body)
+ if err != nil {
+ return err
+ }
+ }
+
+ for {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-time.After(ws.PollInterval):
+ if err := checkTarget(ctx, target); err != nil {
+ return err
+ }
+ req, err := http.NewRequestWithContext(ctx, ws.Method, endpoint.String(), bytes.NewReader(body))
+ if err != nil {
+ return err
+ }
+
+ for k, v := range ws.Headers {
+ req.Header.Set(k, v)
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ continue
+ }
+ if ws.StatusCodeMatcher != nil && !ws.StatusCodeMatcher(resp.StatusCode) {
+ _ = resp.Body.Close()
+ continue
+ }
+ if ws.ResponseMatcher != nil && !ws.ResponseMatcher(resp.Body) {
+ _ = resp.Body.Close()
+ continue
+ }
+ if ws.ResponseHeadersMatcher != nil && !ws.ResponseHeadersMatcher(resp.Header) {
+ _ = resp.Body.Close()
+ continue
+ }
+ if err := resp.Body.Close(); err != nil {
+ continue
+ }
+ return nil
+ }
+ }
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/log.go b/vendor/github.com/testcontainers/testcontainers-go/wait/log.go
new file mode 100644
index 0000000..41c96e3
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/log.go
@@ -0,0 +1,214 @@
+package wait
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "regexp"
+ "time"
+)
+
+// Implement interface
+var (
+ _ Strategy = (*LogStrategy)(nil)
+ _ StrategyTimeout = (*LogStrategy)(nil)
+)
+
+// PermanentError is a special error that will stop the wait and return an error.
+type PermanentError struct {
+ err error
+}
+
+// Error implements the error interface.
+func (e *PermanentError) Error() string {
+ return e.err.Error()
+}
+
+// NewPermanentError creates a new PermanentError.
+func NewPermanentError(err error) *PermanentError {
+ return &PermanentError{err: err}
+}
+
+// LogStrategy will wait until a given log entry shows up in the docker logs
+type LogStrategy struct {
+ // all Strategies should have a startupTimeout to avoid waiting infinitely
+ timeout *time.Duration
+
+ // additional properties
+ Log string
+ IsRegexp bool
+ Occurrence int
+ PollInterval time.Duration
+
+ // check is the function that will be called to check if the log entry is present.
+ check func([]byte) error
+
+ // submatchCallback is a callback that will be called with the sub matches of the regexp.
+ submatchCallback func(pattern string, matches [][][]byte) error
+
+ // re is the optional compiled regexp.
+ re *regexp.Regexp
+
+ // log byte slice version of [LogStrategy.Log] used for count checks.
+ log []byte
+}
+
+// NewLogStrategy constructs with polling interval of 100 milliseconds and startup timeout of 60 seconds by default
+func NewLogStrategy(log string) *LogStrategy {
+ return &LogStrategy{
+ Log: log,
+ IsRegexp: false,
+ Occurrence: 1,
+ PollInterval: defaultPollInterval(),
+ }
+}
+
+// fluent builders for each property
+// since go has neither covariance nor generics, the return type must be the type of the concrete implementation
+// this is true for all properties, even the "shared" ones like startupTimeout
+
+// AsRegexp can be used to change the default behavior of the log strategy to use regexp instead of plain text
+func (ws *LogStrategy) AsRegexp() *LogStrategy {
+ ws.IsRegexp = true
+ return ws
+}
+
+// Submatch configures a function that will be called with the result of
+// [regexp.Regexp.FindAllSubmatch], allowing the caller to process the results.
+// If the callback returns nil, the strategy will be considered successful.
+// Returning a [PermanentError] will stop the wait and return an error, otherwise
+// it will retry until the timeout is reached.
+// [LogStrategy.Occurrence] is ignored if this option is set.
+func (ws *LogStrategy) Submatch(callback func(pattern string, matches [][][]byte) error) *LogStrategy {
+ ws.submatchCallback = callback
+
+ return ws
+}
+
+// WithStartupTimeout can be used to change the default startup timeout
+func (ws *LogStrategy) WithStartupTimeout(timeout time.Duration) *LogStrategy {
+ ws.timeout = &timeout
+ return ws
+}
+
+// WithPollInterval can be used to override the default polling interval of 100 milliseconds
+func (ws *LogStrategy) WithPollInterval(pollInterval time.Duration) *LogStrategy {
+ ws.PollInterval = pollInterval
+ return ws
+}
+
+func (ws *LogStrategy) WithOccurrence(o int) *LogStrategy {
+ // the number of occurrence needs to be positive
+ if o <= 0 {
+ o = 1
+ }
+ ws.Occurrence = o
+ return ws
+}
+
+// ForLog is the default construction for the fluid interface.
+//
+// For Example:
+//
+// wait.
+// ForLog("some text").
+// WithPollInterval(1 * time.Second)
+func ForLog(log string) *LogStrategy {
+ return NewLogStrategy(log)
+}
+
+func (ws *LogStrategy) Timeout() *time.Duration {
+ return ws.timeout
+}
+
+// WaitUntilReady implements Strategy.WaitUntilReady
+func (ws *LogStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) error {
+ timeout := defaultStartupTimeout()
+ if ws.timeout != nil {
+ timeout = *ws.timeout
+ }
+
+ switch {
+ case ws.submatchCallback != nil:
+ ws.re = regexp.MustCompile(ws.Log)
+ ws.check = ws.checkSubmatch
+ case ws.IsRegexp:
+ ws.re = regexp.MustCompile(ws.Log)
+ ws.check = ws.checkRegexp
+ default:
+ ws.log = []byte(ws.Log)
+ ws.check = ws.checkCount
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, timeout)
+ defer cancel()
+
+ var lastLen int
+ var lastError error
+ for {
+ select {
+ case <-ctx.Done():
+ return errors.Join(lastError, ctx.Err())
+ default:
+ checkErr := checkTarget(ctx, target)
+
+ reader, err := target.Logs(ctx)
+ if err != nil {
+ // TODO: fix as this will wait for timeout if the logs are not available.
+ time.Sleep(ws.PollInterval)
+ continue
+ }
+
+ b, err := io.ReadAll(reader)
+ if err != nil {
+ // TODO: fix as this will wait for timeout if the logs are not readable.
+ time.Sleep(ws.PollInterval)
+ continue
+ }
+
+ if lastLen == len(b) && checkErr != nil {
+ // Log length hasn't changed so we're not making progress.
+ return checkErr
+ }
+
+ if err := ws.check(b); err != nil {
+ var errPermanent *PermanentError
+ if errors.As(err, &errPermanent) {
+ return err
+ }
+
+ lastError = err
+ lastLen = len(b)
+ time.Sleep(ws.PollInterval)
+ continue
+ }
+
+ return nil
+ }
+ }
+}
+
+// checkCount checks if the log entry is present in the logs using a string count.
+func (ws *LogStrategy) checkCount(b []byte) error {
+ if count := bytes.Count(b, ws.log); count < ws.Occurrence {
+ return fmt.Errorf("%q matched %d times, expected %d", ws.Log, count, ws.Occurrence)
+ }
+
+ return nil
+}
+
+// checkRegexp checks if the log entry is present in the logs using a regexp count.
+func (ws *LogStrategy) checkRegexp(b []byte) error {
+ if matches := ws.re.FindAll(b, -1); len(matches) < ws.Occurrence {
+ return fmt.Errorf("`%s` matched %d times, expected %d", ws.Log, len(matches), ws.Occurrence)
+ }
+
+ return nil
+}
+
+// checkSubmatch checks if the log entry is present in the logs using a regexp sub match callback.
+func (ws *LogStrategy) checkSubmatch(b []byte) error {
+ return ws.submatchCallback(ws.Log, ws.re.FindAllSubmatch(b, -1))
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/nop.go b/vendor/github.com/testcontainers/testcontainers-go/wait/nop.go
new file mode 100644
index 0000000..c47d83d
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/nop.go
@@ -0,0 +1,81 @@
+package wait
+
+import (
+ "context"
+ "io"
+ "time"
+
+ "github.com/docker/docker/api/types/container"
+ "github.com/docker/go-connections/nat"
+
+ "github.com/testcontainers/testcontainers-go/exec"
+)
+
+var (
+ _ Strategy = (*NopStrategy)(nil)
+ _ StrategyTimeout = (*NopStrategy)(nil)
+)
+
+type NopStrategy struct {
+ timeout *time.Duration
+ waitUntilReady func(context.Context, StrategyTarget) error
+}
+
+func ForNop(
+ waitUntilReady func(context.Context, StrategyTarget) error,
+) *NopStrategy {
+ return &NopStrategy{
+ waitUntilReady: waitUntilReady,
+ }
+}
+
+func (ws *NopStrategy) Timeout() *time.Duration {
+ return ws.timeout
+}
+
+func (ws *NopStrategy) WithStartupTimeout(timeout time.Duration) *NopStrategy {
+ ws.timeout = &timeout
+ return ws
+}
+
+func (ws *NopStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) error {
+ return ws.waitUntilReady(ctx, target)
+}
+
+type NopStrategyTarget struct {
+ ReaderCloser io.ReadCloser
+ ContainerState container.State
+}
+
+func (st NopStrategyTarget) Host(_ context.Context) (string, error) {
+ return "", nil
+}
+
+func (st NopStrategyTarget) Inspect(_ context.Context) (*container.InspectResponse, error) {
+ return nil, nil
+}
+
+// Deprecated: use Inspect instead
+func (st NopStrategyTarget) Ports(_ context.Context) (nat.PortMap, error) {
+ return nil, nil
+}
+
+func (st NopStrategyTarget) MappedPort(_ context.Context, n nat.Port) (nat.Port, error) {
+ return n, nil
+}
+
+func (st NopStrategyTarget) Logs(_ context.Context) (io.ReadCloser, error) {
+ return st.ReaderCloser, nil
+}
+
+func (st NopStrategyTarget) Exec(_ context.Context, _ []string, _ ...exec.ProcessOption) (int, io.Reader, error) {
+ return 0, nil, nil
+}
+
+func (st NopStrategyTarget) State(_ context.Context) (*container.State, error) {
+ return &st.ContainerState, nil
+}
+
+func (st NopStrategyTarget) CopyFileFromContainer(context.Context, string) (io.ReadCloser, error) {
+ return st.ReaderCloser, nil
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/sql.go b/vendor/github.com/testcontainers/testcontainers-go/wait/sql.go
new file mode 100644
index 0000000..1d09eda
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/sql.go
@@ -0,0 +1,118 @@
+package wait
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "time"
+
+ "github.com/docker/go-connections/nat"
+)
+
+var (
+ _ Strategy = (*waitForSQL)(nil)
+ _ StrategyTimeout = (*waitForSQL)(nil)
+)
+
+const defaultForSQLQuery = "SELECT 1"
+
+// ForSQL constructs a new waitForSql strategy for the given driver
+func ForSQL(port nat.Port, driver string, url func(host string, port nat.Port) string) *waitForSQL {
+ return &waitForSQL{
+ Port: port,
+ URL: url,
+ Driver: driver,
+ startupTimeout: defaultStartupTimeout(),
+ PollInterval: defaultPollInterval(),
+ query: defaultForSQLQuery,
+ }
+}
+
+type waitForSQL struct {
+ timeout *time.Duration
+
+ URL func(host string, port nat.Port) string
+ Driver string
+ Port nat.Port
+ startupTimeout time.Duration
+ PollInterval time.Duration
+ query string
+}
+
+// WithStartupTimeout can be used to change the default startup timeout
+func (w *waitForSQL) WithStartupTimeout(timeout time.Duration) *waitForSQL {
+ w.timeout = &timeout
+ return w
+}
+
+// WithPollInterval can be used to override the default polling interval of 100 milliseconds
+func (w *waitForSQL) WithPollInterval(pollInterval time.Duration) *waitForSQL {
+ w.PollInterval = pollInterval
+ return w
+}
+
+// WithQuery can be used to override the default query used in the strategy.
+func (w *waitForSQL) WithQuery(query string) *waitForSQL {
+ w.query = query
+ return w
+}
+
+func (w *waitForSQL) Timeout() *time.Duration {
+ return w.timeout
+}
+
+// WaitUntilReady repeatedly tries to run "SELECT 1" or user defined query on the given port using sql and driver.
+//
+// If it doesn't succeed until the timeout value which defaults to 60 seconds, it will return an error.
+func (w *waitForSQL) WaitUntilReady(ctx context.Context, target StrategyTarget) error {
+ timeout := defaultStartupTimeout()
+ if w.timeout != nil {
+ timeout = *w.timeout
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, timeout)
+ defer cancel()
+
+ host, err := target.Host(ctx)
+ if err != nil {
+ return err
+ }
+
+ ticker := time.NewTicker(w.PollInterval)
+ defer ticker.Stop()
+
+ var port nat.Port
+ port, err = target.MappedPort(ctx, w.Port)
+
+ for port == "" {
+ select {
+ case <-ctx.Done():
+ return fmt.Errorf("%w: %w", ctx.Err(), err)
+ case <-ticker.C:
+ if err := checkTarget(ctx, target); err != nil {
+ return err
+ }
+ port, err = target.MappedPort(ctx, w.Port)
+ }
+ }
+
+ db, err := sql.Open(w.Driver, w.URL(host, port))
+ if err != nil {
+ return fmt.Errorf("sql.Open: %w", err)
+ }
+ defer db.Close()
+ for {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-ticker.C:
+ if err := checkTarget(ctx, target); err != nil {
+ return err
+ }
+ if _, err := db.ExecContext(ctx, w.query); err != nil {
+ continue
+ }
+ return nil
+ }
+ }
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/tls.go b/vendor/github.com/testcontainers/testcontainers-go/wait/tls.go
new file mode 100644
index 0000000..ab904b2
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/tls.go
@@ -0,0 +1,167 @@
+package wait
+
+import (
+ "context"
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "io"
+ "time"
+)
+
+// Validate we implement interface.
+var _ Strategy = (*TLSStrategy)(nil)
+
+// TLSStrategy is a strategy for handling TLS.
+type TLSStrategy struct {
+ // General Settings.
+ timeout *time.Duration
+ pollInterval time.Duration
+
+ // Custom Settings.
+ certFiles *x509KeyPair
+ rootFiles []string
+
+ // State.
+ tlsConfig *tls.Config
+}
+
+// x509KeyPair is a pair of certificate and key files.
+type x509KeyPair struct {
+ certPEMFile string
+ keyPEMFile string
+}
+
+// ForTLSCert returns a CertStrategy that will add a Certificate to the [tls.Config]
+// constructed from PEM formatted certificate key file pair in the container.
+func ForTLSCert(certPEMFile, keyPEMFile string) *TLSStrategy {
+ return &TLSStrategy{
+ certFiles: &x509KeyPair{
+ certPEMFile: certPEMFile,
+ keyPEMFile: keyPEMFile,
+ },
+ tlsConfig: &tls.Config{},
+ pollInterval: defaultPollInterval(),
+ }
+}
+
+// ForTLSRootCAs returns a CertStrategy that sets the root CAs for the [tls.Config]
+// using the given PEM formatted files from the container.
+func ForTLSRootCAs(pemFiles ...string) *TLSStrategy {
+ return &TLSStrategy{
+ rootFiles: pemFiles,
+ tlsConfig: &tls.Config{},
+ pollInterval: defaultPollInterval(),
+ }
+}
+
+// WithRootCAs sets the root CAs for the [tls.Config] using the given files from
+// the container.
+func (ws *TLSStrategy) WithRootCAs(files ...string) *TLSStrategy {
+ ws.rootFiles = files
+ return ws
+}
+
+// WithCert sets the [tls.Config] Certificates using the given files from the container.
+func (ws *TLSStrategy) WithCert(certPEMFile, keyPEMFile string) *TLSStrategy {
+ ws.certFiles = &x509KeyPair{
+ certPEMFile: certPEMFile,
+ keyPEMFile: keyPEMFile,
+ }
+ return ws
+}
+
+// WithServerName sets the server for the [tls.Config].
+func (ws *TLSStrategy) WithServerName(serverName string) *TLSStrategy {
+ ws.tlsConfig.ServerName = serverName
+ return ws
+}
+
+// WithStartupTimeout can be used to change the default startup timeout.
+func (ws *TLSStrategy) WithStartupTimeout(startupTimeout time.Duration) *TLSStrategy {
+ ws.timeout = &startupTimeout
+ return ws
+}
+
+// WithPollInterval can be used to override the default polling interval of 100 milliseconds.
+func (ws *TLSStrategy) WithPollInterval(pollInterval time.Duration) *TLSStrategy {
+ ws.pollInterval = pollInterval
+ return ws
+}
+
+// TLSConfig returns the TLS config once the strategy is ready.
+// If the strategy is nil, it returns nil.
+func (ws *TLSStrategy) TLSConfig() *tls.Config {
+ if ws == nil {
+ return nil
+ }
+
+ return ws.tlsConfig
+}
+
+// WaitUntilReady implements the [Strategy] interface.
+// It waits for the CA, client cert and key files to be available in the container and
+// uses them to setup the TLS config.
+func (ws *TLSStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) error {
+ size := len(ws.rootFiles)
+ if ws.certFiles != nil {
+ size += 2
+ }
+ strategies := make([]Strategy, 0, size)
+ for _, file := range ws.rootFiles {
+ strategies = append(strategies,
+ ForFile(file).WithMatcher(func(r io.Reader) error {
+ buf, err := io.ReadAll(r)
+ if err != nil {
+ return fmt.Errorf("read CA cert file %q: %w", file, err)
+ }
+
+ if ws.tlsConfig.RootCAs == nil {
+ ws.tlsConfig.RootCAs = x509.NewCertPool()
+ }
+
+ if !ws.tlsConfig.RootCAs.AppendCertsFromPEM(buf) {
+ return fmt.Errorf("invalid CA cert file %q", file)
+ }
+
+ return nil
+ }).WithPollInterval(ws.pollInterval),
+ )
+ }
+
+ if ws.certFiles != nil {
+ var certPEMBlock []byte
+ strategies = append(strategies,
+ ForFile(ws.certFiles.certPEMFile).WithMatcher(func(r io.Reader) error {
+ var err error
+ if certPEMBlock, err = io.ReadAll(r); err != nil {
+ return fmt.Errorf("read certificate cert %q: %w", ws.certFiles.certPEMFile, err)
+ }
+
+ return nil
+ }).WithPollInterval(ws.pollInterval),
+ ForFile(ws.certFiles.keyPEMFile).WithMatcher(func(r io.Reader) error {
+ keyPEMBlock, err := io.ReadAll(r)
+ if err != nil {
+ return fmt.Errorf("read certificate key %q: %w", ws.certFiles.keyPEMFile, err)
+ }
+
+ cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
+ if err != nil {
+ return fmt.Errorf("x509 key pair %q %q: %w", ws.certFiles.certPEMFile, ws.certFiles.keyPEMFile, err)
+ }
+
+ ws.tlsConfig.Certificates = []tls.Certificate{cert}
+
+ return nil
+ }).WithPollInterval(ws.pollInterval),
+ )
+ }
+
+ strategy := ForAll(strategies...)
+ if ws.timeout != nil {
+ strategy.WithStartupTimeout(*ws.timeout)
+ }
+
+ return strategy.WaitUntilReady(ctx, target)
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/wait.go b/vendor/github.com/testcontainers/testcontainers-go/wait/wait.go
new file mode 100644
index 0000000..ca5a7db
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/wait.go
@@ -0,0 +1,65 @@
+package wait
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "time"
+
+ "github.com/docker/docker/api/types/container"
+ "github.com/docker/go-connections/nat"
+
+ "github.com/testcontainers/testcontainers-go/exec"
+)
+
+// Strategy defines the basic interface for a Wait Strategy
+type Strategy interface {
+ WaitUntilReady(context.Context, StrategyTarget) error
+}
+
+// StrategyTimeout allows MultiStrategy to configure a Strategy's Timeout
+type StrategyTimeout interface {
+ Timeout() *time.Duration
+}
+
+type StrategyTarget interface {
+ Host(context.Context) (string, error)
+ Inspect(context.Context) (*container.InspectResponse, error)
+ Ports(ctx context.Context) (nat.PortMap, error) // Deprecated: use Inspect instead
+ MappedPort(context.Context, nat.Port) (nat.Port, error)
+ Logs(context.Context) (io.ReadCloser, error)
+ Exec(context.Context, []string, ...exec.ProcessOption) (int, io.Reader, error)
+ State(context.Context) (*container.State, error)
+ CopyFileFromContainer(ctx context.Context, filePath string) (io.ReadCloser, error)
+}
+
+func checkTarget(ctx context.Context, target StrategyTarget) error {
+ state, err := target.State(ctx)
+ if err != nil {
+ return fmt.Errorf("get state: %w", err)
+ }
+
+ return checkState(state)
+}
+
+func checkState(state *container.State) error {
+ switch {
+ case state.Running:
+ return nil
+ case state.OOMKilled:
+ return errors.New("container crashed with out-of-memory (OOMKilled)")
+ case state.Status == "exited":
+ return fmt.Errorf("container exited with code %d", state.ExitCode)
+ default:
+ return fmt.Errorf("unexpected container status %q", state.Status)
+ }
+}
+
+func defaultStartupTimeout() time.Duration {
+ return 60 * time.Second
+}
+
+func defaultPollInterval() time.Duration {
+ return 100 * time.Millisecond
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/wait/walk.go b/vendor/github.com/testcontainers/testcontainers-go/wait/walk.go
new file mode 100644
index 0000000..4685e50
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/wait/walk.go
@@ -0,0 +1,74 @@
+package wait
+
+import (
+ "errors"
+)
+
+var (
+ // VisitStop is used as a return value from [VisitFunc] to stop the walk.
+ // It is not returned as an error by any function.
+ VisitStop = errors.New("stop the walk")
+
+ // VisitRemove is used as a return value from [VisitFunc] to have the current node removed.
+ // It is not returned as an error by any function.
+ VisitRemove = errors.New("remove this strategy")
+)
+
+// VisitFunc is a function that visits a strategy node.
+// If it returns [VisitStop], the walk stops.
+// If it returns [VisitRemove], the current node is removed.
+type VisitFunc func(root Strategy) error
+
+// Walk walks the strategies tree and calls the visit function for each node.
+func Walk(root *Strategy, visit VisitFunc) error {
+ if root == nil {
+ return errors.New("root strategy is nil")
+ }
+
+ if err := walk(root, visit); err != nil {
+ if errors.Is(err, VisitRemove) || errors.Is(err, VisitStop) {
+ return nil
+ }
+ return err
+ }
+
+ return nil
+}
+
+// walk walks the strategies tree and calls the visit function for each node.
+// It returns an error if the visit function returns an error.
+func walk(root *Strategy, visit VisitFunc) error {
+ if *root == nil {
+ // No strategy.
+ return nil
+ }
+
+ // Allow the visit function to customize the behaviour of the walk before visiting the children.
+ if err := visit(*root); err != nil {
+ if errors.Is(err, VisitRemove) {
+ *root = nil
+ }
+
+ return err
+ }
+
+ if s, ok := (*root).(*MultiStrategy); ok {
+ var i int
+ for range s.Strategies {
+ if err := walk(&s.Strategies[i], visit); err != nil {
+ if errors.Is(err, VisitRemove) {
+ s.Strategies = append(s.Strategies[:i], s.Strategies[i+1:]...)
+ if errors.Is(err, VisitStop) {
+ return VisitStop
+ }
+ continue
+ }
+
+ return err
+ }
+ i++
+ }
+ }
+
+ return nil
+}