summaryrefslogtreecommitdiff
path: root/vendor/github.com/testcontainers/testcontainers-go/internal
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/internal
parent05ca9b8d3a9c7203a3a3b590beaa400900bd9007 (diff)
chore: vendor go dependencies
Diffstat (limited to 'vendor/github.com/testcontainers/testcontainers-go/internal')
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/internal/config/config.go185
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/internal/core/bootstrap.go106
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/internal/core/client.go50
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/internal/core/docker_host.go329
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/internal/core/docker_rootless.go150
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/internal/core/docker_socket.go49
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/internal/core/images.go132
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/internal/core/labels.go73
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/internal/core/network/network.go52
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/internal/version.go4
10 files changed, 1130 insertions, 0 deletions
diff --git a/vendor/github.com/testcontainers/testcontainers-go/internal/config/config.go b/vendor/github.com/testcontainers/testcontainers-go/internal/config/config.go
new file mode 100644
index 0000000..64f2f7f
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/internal/config/config.go
@@ -0,0 +1,185 @@
+package config
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/magiconair/properties"
+)
+
+const ReaperDefaultImage = "testcontainers/ryuk:0.11.0"
+
+var (
+ tcConfig Config
+ tcConfigOnce = new(sync.Once)
+)
+
+// testcontainersConfig {
+
+// Config represents the configuration for Testcontainers.
+// User values are read from ~/.testcontainers.properties file which can be overridden
+// using the specified environment variables. For more information, see [Custom Configuration].
+//
+// The Ryuk prefixed fields controls the [Garbage Collector] feature, which ensures that
+// resources are cleaned up after the test execution.
+//
+// [Garbage Collector]: https://golang.testcontainers.org/features/garbage_collector/
+// [Custom Configuration]: https://golang.testcontainers.org/features/configuration/
+type Config struct {
+ // Host is the address of the Docker daemon.
+ //
+ // Environment variable: DOCKER_HOST
+ Host string `properties:"docker.host,default="`
+
+ // TLSVerify is a flag to enable or disable TLS verification when connecting to a Docker daemon.
+ //
+ // Environment variable: DOCKER_TLS_VERIFY
+ TLSVerify int `properties:"docker.tls.verify,default=0"`
+
+ // CertPath is the path to the directory containing the Docker certificates.
+ // This is used when connecting to a Docker daemon over TLS.
+ //
+ // Environment variable: DOCKER_CERT_PATH
+ CertPath string `properties:"docker.cert.path,default="`
+
+ // HubImageNamePrefix is the prefix used for the images pulled from the Docker Hub.
+ // This is useful when running tests in environments with restricted internet access.
+ //
+ // Environment variable: TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX
+ HubImageNamePrefix string `properties:"hub.image.name.prefix,default="`
+
+ // RyukDisabled is a flag to enable or disable the Garbage Collector.
+ // Setting this to true will prevent testcontainers from automatically cleaning up
+ // resources, which is particularly important in tests which timeout as they
+ // don't run test clean up.
+ //
+ // Environment variable: TESTCONTAINERS_RYUK_DISABLED
+ RyukDisabled bool `properties:"ryuk.disabled,default=false"`
+
+ // RyukPrivileged is a flag to enable or disable the privileged mode for the Garbage Collector container.
+ // Setting this to true will run the Garbage Collector container in privileged mode.
+ //
+ // Environment variable: TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED
+ RyukPrivileged bool `properties:"ryuk.container.privileged,default=false"`
+
+ // RyukReconnectionTimeout is the time to wait before attempting to reconnect to the Garbage Collector container.
+ //
+ // Environment variable: RYUK_RECONNECTION_TIMEOUT
+ RyukReconnectionTimeout time.Duration `properties:"ryuk.reconnection.timeout,default=10s"`
+
+ // RyukConnectionTimeout is the time to wait before timing out when connecting to the Garbage Collector container.
+ //
+ // Environment variable: RYUK_CONNECTION_TIMEOUT
+ RyukConnectionTimeout time.Duration `properties:"ryuk.connection.timeout,default=1m"`
+
+ // RyukVerbose is a flag to enable or disable verbose logging for the Garbage Collector.
+ //
+ // Environment variable: RYUK_VERBOSE
+ RyukVerbose bool `properties:"ryuk.verbose,default=false"`
+
+ // TestcontainersHost is the address of the Testcontainers host.
+ //
+ // Environment variable: TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE
+ TestcontainersHost string `properties:"tc.host,default="`
+}
+
+// }
+
+// Read reads from testcontainers properties file, if it exists
+// it is possible that certain values get overridden when set as environment variables
+func Read() Config {
+ tcConfigOnce.Do(func() {
+ tcConfig = read()
+ })
+
+ return tcConfig
+}
+
+// Reset resets the singleton instance of the Config struct,
+// allowing to read the configuration again.
+// Handy for testing, so do not use it in production code
+// This function is not thread-safe
+func Reset() {
+ tcConfigOnce = new(sync.Once)
+}
+
+func read() Config {
+ config := Config{}
+
+ applyEnvironmentConfiguration := func(config Config) Config {
+ ryukDisabledEnv := os.Getenv("TESTCONTAINERS_RYUK_DISABLED")
+ if parseBool(ryukDisabledEnv) {
+ config.RyukDisabled = ryukDisabledEnv == "true"
+ }
+
+ hubImageNamePrefix := os.Getenv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX")
+ if hubImageNamePrefix != "" {
+ config.HubImageNamePrefix = hubImageNamePrefix
+ }
+
+ ryukPrivilegedEnv := os.Getenv("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED")
+ if parseBool(ryukPrivilegedEnv) {
+ config.RyukPrivileged = ryukPrivilegedEnv == "true"
+ }
+
+ ryukVerboseEnv := readTestcontainersEnv("RYUK_VERBOSE")
+ if parseBool(ryukVerboseEnv) {
+ config.RyukVerbose = ryukVerboseEnv == "true"
+ }
+
+ ryukReconnectionTimeoutEnv := readTestcontainersEnv("RYUK_RECONNECTION_TIMEOUT")
+ if timeout, err := time.ParseDuration(ryukReconnectionTimeoutEnv); err == nil {
+ config.RyukReconnectionTimeout = timeout
+ }
+
+ ryukConnectionTimeoutEnv := readTestcontainersEnv("RYUK_CONNECTION_TIMEOUT")
+ if timeout, err := time.ParseDuration(ryukConnectionTimeoutEnv); err == nil {
+ config.RyukConnectionTimeout = timeout
+ }
+
+ return config
+ }
+
+ home, err := os.UserHomeDir()
+ if err != nil {
+ return applyEnvironmentConfiguration(config)
+ }
+
+ tcProp := filepath.Join(home, ".testcontainers.properties")
+ // init from a file
+ properties, err := properties.LoadFile(tcProp, properties.UTF8)
+ if err != nil {
+ return applyEnvironmentConfiguration(config)
+ }
+
+ if err := properties.Decode(&config); err != nil {
+ fmt.Printf("invalid testcontainers properties file, returning an empty Testcontainers configuration: %v\n", err)
+ return applyEnvironmentConfiguration(config)
+ }
+
+ return applyEnvironmentConfiguration(config)
+}
+
+func parseBool(input string) bool {
+ _, err := strconv.ParseBool(input)
+ return err == nil
+}
+
+// readTestcontainersEnv reads the environment variable with the given name.
+// It checks for the environment variable with the given name first, and then
+// checks for the environment variable with the given name prefixed with "TESTCONTAINERS_".
+func readTestcontainersEnv(envVar string) string {
+ value := os.Getenv(envVar)
+ if value != "" {
+ return value
+ }
+
+ // TODO: remove this prefix after the next major release
+ const prefix string = "TESTCONTAINERS_"
+
+ return os.Getenv(prefix + envVar)
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/internal/core/bootstrap.go b/vendor/github.com/testcontainers/testcontainers-go/internal/core/bootstrap.go
new file mode 100644
index 0000000..201d4b0
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/internal/core/bootstrap.go
@@ -0,0 +1,106 @@
+package core
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "os"
+
+ "github.com/google/uuid"
+ "github.com/shirou/gopsutil/v4/process"
+)
+
+// sessionID returns a unique session ID for the current test session. Because each Go package
+// will be run in a separate process, we need a way to identify the current test session.
+// By test session, we mean:
+// - a single "go test" invocation (including flags)
+// - a single "go test ./..." invocation (including flags)
+// - the execution of a single test or a set of tests using the IDE
+//
+// As a consequence, with the sole goal of aggregating test execution across multiple
+// packages, this function will use the parent process ID (pid) of the current process
+// and its creation date, to use it to generate a unique session ID. We are using the parent pid because
+// the current process will be a child process of:
+// - the process that is running the tests, e.g.: "go test";
+// - the process that is running the application in development mode, e.g. "go run main.go -tags dev";
+// - the process that is running the tests in the IDE, e.g.: "go test ./...".
+//
+// Finally, we will hash the combination of the "testcontainers-go:" string with the parent pid
+// and the creation date of that parent process to generate a unique session ID.
+//
+// This sessionID will be used to:
+// - identify the test session, aggregating the test execution of multiple packages in the same test session.
+// - tag the containers created by testcontainers-go, adding a label to the container with the session ID.
+var sessionID string
+
+// projectPath returns the current working directory of the parent test process running Testcontainers for Go.
+// If it's not possible to get that directory, the library will use the current working directory. If again
+// it's not possible to get the current working directory, the library will use a temporary directory.
+var projectPath string
+
+// processID returns a unique ID for the current test process. Because each Go package will be run in a separate process,
+// we need a way to identify the current test process, in the form of a UUID
+var processID string
+
+const sessionIDPlaceholder = "testcontainers-go:%d:%d"
+
+func init() {
+ processID = uuid.New().String()
+
+ parentPid := os.Getppid()
+ var createTime int64
+ fallbackCwd, err := os.Getwd()
+ if err != nil {
+ // very unlikely to fail, but if it does, we will use a temp dir
+ fallbackCwd = os.TempDir()
+ }
+
+ processes, err := process.Processes()
+ if err != nil {
+ sessionID = uuid.New().String()
+ projectPath = fallbackCwd
+ return
+ }
+
+ for _, p := range processes {
+ if int(p.Pid) != parentPid {
+ continue
+ }
+
+ cwd, err := p.Cwd()
+ if err != nil {
+ cwd = fallbackCwd
+ }
+ projectPath = cwd
+
+ t, err := p.CreateTime()
+ if err != nil {
+ sessionID = uuid.New().String()
+ return
+ }
+
+ createTime = t
+ break
+ }
+
+ hasher := sha256.New()
+ _, err = hasher.Write([]byte(fmt.Sprintf(sessionIDPlaceholder, parentPid, createTime)))
+ if err != nil {
+ sessionID = uuid.New().String()
+ return
+ }
+
+ sessionID = hex.EncodeToString(hasher.Sum(nil))
+}
+
+func ProcessID() string {
+ return processID
+}
+
+func ProjectPath() string {
+ return projectPath
+}
+
+func SessionID() string {
+ return sessionID
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/internal/core/client.go b/vendor/github.com/testcontainers/testcontainers-go/internal/core/client.go
new file mode 100644
index 0000000..04a54bc
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/internal/core/client.go
@@ -0,0 +1,50 @@
+package core
+
+import (
+ "context"
+ "path/filepath"
+
+ "github.com/docker/docker/client"
+
+ "github.com/testcontainers/testcontainers-go/internal"
+ "github.com/testcontainers/testcontainers-go/internal/config"
+)
+
+// NewClient returns a new docker client extracting the docker host from the different alternatives
+func NewClient(ctx context.Context, ops ...client.Opt) (*client.Client, error) {
+ tcConfig := config.Read()
+
+ dockerHost := MustExtractDockerHost(ctx)
+
+ opts := []client.Opt{client.FromEnv, client.WithAPIVersionNegotiation()}
+ if dockerHost != "" {
+ opts = append(opts, client.WithHost(dockerHost))
+
+ // for further information, read https://docs.docker.com/engine/security/protect-access/
+ if tcConfig.TLSVerify == 1 {
+ cacertPath := filepath.Join(tcConfig.CertPath, "ca.pem")
+ certPath := filepath.Join(tcConfig.CertPath, "cert.pem")
+ keyPath := filepath.Join(tcConfig.CertPath, "key.pem")
+
+ opts = append(opts, client.WithTLSClientConfig(cacertPath, certPath, keyPath))
+ }
+ }
+
+ opts = append(opts, client.WithHTTPHeaders(
+ map[string]string{
+ "x-tc-pp": ProjectPath(),
+ "x-tc-sid": SessionID(),
+ "User-Agent": "tc-go/" + internal.Version,
+ }),
+ )
+
+ // passed options have priority over the default ones
+ opts = append(opts, ops...)
+
+ cli, err := client.NewClientWithOpts(opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ return cli, nil
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/internal/core/docker_host.go b/vendor/github.com/testcontainers/testcontainers-go/internal/core/docker_host.go
new file mode 100644
index 0000000..fc06ea8
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/internal/core/docker_host.go
@@ -0,0 +1,329 @@
+package core
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+ "sync"
+
+ "github.com/docker/docker/client"
+
+ "github.com/testcontainers/testcontainers-go/internal/config"
+)
+
+type dockerHostContext string
+
+var DockerHostContextKey = dockerHostContext("docker_host")
+
+var (
+ ErrDockerHostNotSet = errors.New("DOCKER_HOST is not set")
+ ErrDockerSocketOverrideNotSet = errors.New("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE is not set")
+ ErrDockerSocketNotSetInContext = errors.New("socket not set in context")
+ ErrDockerSocketNotSetInProperties = errors.New("socket not set in ~/.testcontainers.properties")
+ ErrNoUnixSchema = errors.New("URL schema is not unix")
+ ErrSocketNotFound = errors.New("socket not found")
+ ErrSocketNotFoundInPath = errors.New("docker socket not found in " + DockerSocketPath)
+ // ErrTestcontainersHostNotSetInProperties this error is specific to Testcontainers
+ ErrTestcontainersHostNotSetInProperties = errors.New("tc.host not set in ~/.testcontainers.properties")
+)
+
+var (
+ dockerHostCache string
+ dockerHostOnce sync.Once
+)
+
+var (
+ dockerSocketPathCache string
+ dockerSocketPathOnce sync.Once
+)
+
+// deprecated
+// see https://github.com/testcontainers/testcontainers-java/blob/main/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L46
+func DefaultGatewayIP() (string, error) {
+ // see https://github.com/testcontainers/testcontainers-java/blob/3ad8d80e2484864e554744a4800a81f6b7982168/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L27
+ cmd := exec.Command("sh", "-c", "ip route|awk '/default/ { print $3 }'")
+ stdout, err := cmd.Output()
+ if err != nil {
+ return "", errors.New("failed to detect docker host")
+ }
+ ip := strings.TrimSpace(string(stdout))
+ if len(ip) == 0 {
+ return "", errors.New("failed to parse default gateway IP")
+ }
+ return ip, nil
+}
+
+// dockerHostCheck Use a vanilla Docker client to check if the Docker host is reachable.
+// It will avoid recursive calls to this function.
+var dockerHostCheck = func(ctx context.Context, host string) error {
+ cli, err := client.NewClientWithOpts(client.FromEnv, client.WithHost(host), client.WithAPIVersionNegotiation())
+ if err != nil {
+ return fmt.Errorf("new client: %w", err)
+ }
+ defer cli.Close()
+
+ _, err = cli.Info(ctx)
+ if err != nil {
+ return fmt.Errorf("docker info: %w", err)
+ }
+
+ return nil
+}
+
+// MustExtractDockerHost Extracts the docker host from the different alternatives, caching the result to avoid unnecessary
+// calculations. Use this function to get the actual Docker host. This function does not consider Windows containers at the moment.
+// The possible alternatives are:
+//
+// 1. Docker host from the "tc.host" property in the ~/.testcontainers.properties file.
+// 2. DOCKER_HOST environment variable.
+// 3. Docker host from context.
+// 4. Docker host from the default docker socket path, without the unix schema.
+// 5. Docker host from the "docker.host" property in the ~/.testcontainers.properties file.
+// 6. Rootless docker socket path.
+// 7. Else, because the Docker host is not set, it panics.
+func MustExtractDockerHost(ctx context.Context) string {
+ dockerHostOnce.Do(func() {
+ cache, err := extractDockerHost(ctx)
+ if err != nil {
+ panic(err)
+ }
+
+ dockerHostCache = cache
+ })
+
+ return dockerHostCache
+}
+
+// MustExtractDockerSocket Extracts the docker socket from the different alternatives, removing the socket schema and
+// caching the result to avoid unnecessary calculations. Use this function to get the docker socket path,
+// not the host (e.g. mounting the socket in a container). This function does not consider Windows containers at the moment.
+// The possible alternatives are:
+//
+// 1. Docker host from the "tc.host" property in the ~/.testcontainers.properties file.
+// 2. The TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE environment variable.
+// 3. Using a Docker client, check if the Info().OperatingSystem is "Docker Desktop" and return the default docker socket path for rootless docker.
+// 4. Else, Get the current Docker Host from the existing strategies: see MustExtractDockerHost.
+// 5. If the socket contains the unix schema, the schema is removed (e.g. unix:///var/run/docker.sock -> /var/run/docker.sock)
+// 6. Else, the default location of the docker socket is used (/var/run/docker.sock)
+//
+// It panics if a Docker client cannot be created, or the Docker host cannot be discovered.
+func MustExtractDockerSocket(ctx context.Context) string {
+ dockerSocketPathOnce.Do(func() {
+ dockerSocketPathCache = extractDockerSocket(ctx)
+ })
+
+ return dockerSocketPathCache
+}
+
+// extractDockerHost Extracts the docker host from the different alternatives, without caching the result.
+// This internal method is handy for testing purposes.
+func extractDockerHost(ctx context.Context) (string, error) {
+ dockerHostFns := []func(context.Context) (string, error){
+ testcontainersHostFromProperties,
+ dockerHostFromEnv,
+ dockerHostFromContext,
+ dockerSocketPath,
+ dockerHostFromProperties,
+ rootlessDockerSocketPath,
+ }
+
+ var errs []error
+ for _, dockerHostFn := range dockerHostFns {
+ dockerHost, err := dockerHostFn(ctx)
+ if err != nil {
+ if !isHostNotSet(err) {
+ errs = append(errs, err)
+ }
+ continue
+ }
+
+ if err = dockerHostCheck(ctx, dockerHost); err != nil {
+ errs = append(errs, fmt.Errorf("check host %q: %w", dockerHost, err))
+ continue
+ }
+
+ return dockerHost, nil
+ }
+
+ if len(errs) > 0 {
+ return "", errors.Join(errs...)
+ }
+
+ return "", ErrSocketNotFound
+}
+
+// extractDockerSocket Extracts the docker socket from the different alternatives, without caching the result.
+// It will internally use the default Docker client, calling the internal method extractDockerSocketFromClient with it.
+// This internal method is handy for testing purposes.
+// It panics if a Docker client cannot be created, or the Docker host is not discovered.
+func extractDockerSocket(ctx context.Context) string {
+ cli, err := NewClient(ctx)
+ if err != nil {
+ panic(err) // a Docker client is required to get the Docker info
+ }
+ defer cli.Close()
+
+ return extractDockerSocketFromClient(ctx, cli)
+}
+
+// extractDockerSocketFromClient Extracts the docker socket from the different alternatives, without caching the result,
+// and receiving an instance of the Docker API client interface.
+// This internal method is handy for testing purposes, passing a mock type simulating the desired behaviour.
+// It panics if the Docker Info call errors, or the Docker host is not discovered.
+func extractDockerSocketFromClient(ctx context.Context, cli client.APIClient) string {
+ // check that the socket is not a tcp or unix socket
+ checkDockerSocketFn := func(socket string) string {
+ // this use case will cover the case when the docker host is a tcp socket
+ if strings.HasPrefix(socket, TCPSchema) {
+ return DockerSocketPath
+ }
+
+ if strings.HasPrefix(socket, DockerSocketSchema) {
+ return strings.Replace(socket, DockerSocketSchema, "", 1)
+ }
+
+ return socket
+ }
+
+ tcHost, err := testcontainersHostFromProperties(ctx)
+ if err == nil {
+ return checkDockerSocketFn(tcHost)
+ }
+
+ testcontainersDockerSocket, err := dockerSocketOverridePath()
+ if err == nil {
+ return checkDockerSocketFn(testcontainersDockerSocket)
+ }
+
+ info, err := cli.Info(ctx)
+ if err != nil {
+ panic(err) // Docker Info is required to get the Operating System
+ }
+
+ // Because Docker Desktop runs in a VM, we need to use the default docker path for rootless docker
+ if info.OperatingSystem == "Docker Desktop" {
+ if IsWindows() {
+ return WindowsDockerSocketPath
+ }
+
+ return DockerSocketPath
+ }
+
+ dockerHost, err := extractDockerHost(ctx)
+ if err != nil {
+ panic(err) // Docker host is required to get the Docker socket
+ }
+
+ return checkDockerSocketFn(dockerHost)
+}
+
+// isHostNotSet returns true if the error is related to the Docker host
+// not being set, false otherwise.
+func isHostNotSet(err error) bool {
+ switch {
+ case errors.Is(err, ErrTestcontainersHostNotSetInProperties),
+ errors.Is(err, ErrDockerHostNotSet),
+ errors.Is(err, ErrDockerSocketNotSetInContext),
+ errors.Is(err, ErrDockerSocketNotSetInProperties),
+ errors.Is(err, ErrSocketNotFoundInPath),
+ errors.Is(err, ErrXDGRuntimeDirNotSet),
+ errors.Is(err, ErrRootlessDockerNotFoundHomeRunDir),
+ errors.Is(err, ErrRootlessDockerNotFoundHomeDesktopDir),
+ errors.Is(err, ErrRootlessDockerNotFoundRunDir):
+ return true
+ default:
+ return false
+ }
+}
+
+// dockerHostFromEnv returns the docker host from the DOCKER_HOST environment variable, if it's not empty
+func dockerHostFromEnv(_ context.Context) (string, error) {
+ if dockerHostPath := os.Getenv("DOCKER_HOST"); dockerHostPath != "" {
+ return dockerHostPath, nil
+ }
+
+ return "", ErrDockerHostNotSet
+}
+
+// dockerHostFromContext returns the docker host from the Go context, if it's not empty
+func dockerHostFromContext(ctx context.Context) (string, error) {
+ if socketPath, ok := ctx.Value(DockerHostContextKey).(string); ok && socketPath != "" {
+ parsed, err := parseURL(socketPath)
+ if err != nil {
+ return "", err
+ }
+
+ return parsed, nil
+ }
+
+ return "", ErrDockerSocketNotSetInContext
+}
+
+// dockerHostFromProperties returns the docker host from the ~/.testcontainers.properties file, if it's not empty
+func dockerHostFromProperties(_ context.Context) (string, error) {
+ cfg := config.Read()
+ socketPath := cfg.Host
+ if socketPath != "" {
+ return socketPath, nil
+ }
+
+ return "", ErrDockerSocketNotSetInProperties
+}
+
+// dockerSocketOverridePath returns the docker socket from the TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE environment variable,
+// if it's not empty
+func dockerSocketOverridePath() (string, error) {
+ if dockerHostPath, exists := os.LookupEnv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE"); exists {
+ return dockerHostPath, nil
+ }
+
+ return "", ErrDockerSocketOverrideNotSet
+}
+
+// dockerSocketPath returns the docker socket from the default docker socket path, if it's not empty
+// and the socket exists
+func dockerSocketPath(_ context.Context) (string, error) {
+ if fileExists(DockerSocketPath) {
+ return DockerSocketPathWithSchema, nil
+ }
+
+ return "", ErrSocketNotFoundInPath
+}
+
+// testcontainersHostFromProperties returns the testcontainers host from the ~/.testcontainers.properties file, if it's not empty
+func testcontainersHostFromProperties(_ context.Context) (string, error) {
+ cfg := config.Read()
+ testcontainersHost := cfg.TestcontainersHost
+ if testcontainersHost != "" {
+ parsed, err := parseURL(testcontainersHost)
+ if err != nil {
+ return "", err
+ }
+
+ return parsed, nil
+ }
+
+ return "", ErrTestcontainersHostNotSetInProperties
+}
+
+// DockerEnvFile is the file that is created when running inside a container.
+// It's a variable to allow testing.
+// TODO: Remove this once context rework is done, which eliminates need for the default network creation.
+var DockerEnvFile = "/.dockerenv"
+
+// InAContainer returns true if the code is running inside a container
+// See https://github.com/docker/docker/blob/a9fa38b1edf30b23cae3eade0be48b3d4b1de14b/daemon/initlayer/setup_unix.go#L25
+func InAContainer() bool {
+ return inAContainer(DockerEnvFile)
+}
+
+func inAContainer(path string) bool {
+ // see https://github.com/testcontainers/testcontainers-java/blob/3ad8d80e2484864e554744a4800a81f6b7982168/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L15
+ if _, err := os.Stat(path); err == nil {
+ return true
+ }
+ return false
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/internal/core/docker_rootless.go b/vendor/github.com/testcontainers/testcontainers-go/internal/core/docker_rootless.go
new file mode 100644
index 0000000..8108384
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/internal/core/docker_rootless.go
@@ -0,0 +1,150 @@
+package core
+
+import (
+ "context"
+ "errors"
+ "net/url"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+)
+
+var (
+ ErrRootlessDockerNotFound = errors.New("rootless Docker not found")
+ ErrRootlessDockerNotFoundHomeDesktopDir = errors.New("checked path: ~/.docker/desktop/docker.sock")
+ ErrRootlessDockerNotFoundHomeRunDir = errors.New("checked path: ~/.docker/run/docker.sock")
+ ErrRootlessDockerNotFoundRunDir = errors.New("checked path: /run/user/${uid}/docker.sock")
+ ErrRootlessDockerNotFoundXDGRuntimeDir = errors.New("checked path: $XDG_RUNTIME_DIR")
+ ErrRootlessDockerNotSupportedWindows = errors.New("rootless Docker is not supported on Windows")
+ ErrXDGRuntimeDirNotSet = errors.New("XDG_RUNTIME_DIR is not set")
+)
+
+// baseRunDir is the base directory for the "/run/user/${uid}" directory.
+// It is a variable so it can be modified for testing.
+var baseRunDir = "/run"
+
+// IsWindows returns if the current OS is Windows. For that it checks the GOOS environment variable or the runtime.GOOS constant.
+func IsWindows() bool {
+ return os.Getenv("GOOS") == "windows" || runtime.GOOS == "windows"
+}
+
+// rootlessDockerSocketPath returns if the path to the rootless Docker socket exists.
+// The rootless socket path is determined by the following order:
+//
+// 1. XDG_RUNTIME_DIR environment variable.
+// 2. ~/.docker/run/docker.sock file.
+// 3. ~/.docker/desktop/docker.sock file.
+// 4. /run/user/${uid}/docker.sock file.
+// 5. Else, return ErrRootlessDockerNotFound, wrapping specific errors for each of the above paths.
+//
+// It should include the Docker socket schema (unix://) in the returned path.
+func rootlessDockerSocketPath(_ context.Context) (string, error) {
+ // adding a manner to test it on non-windows machines, setting the GOOS env var to windows
+ // This is needed because runtime.GOOS is a constant that returns the OS of the machine running the test
+ if IsWindows() {
+ return "", ErrRootlessDockerNotSupportedWindows
+ }
+
+ socketPathFns := []func() (string, error){
+ rootlessSocketPathFromEnv,
+ rootlessSocketPathFromHomeRunDir,
+ rootlessSocketPathFromHomeDesktopDir,
+ rootlessSocketPathFromRunDir,
+ }
+
+ var errs []error
+ for _, socketPathFn := range socketPathFns {
+ s, err := socketPathFn()
+ if err != nil {
+ if !isHostNotSet(err) {
+ errs = append(errs, err)
+ }
+ continue
+ }
+
+ return DockerSocketSchema + s, nil
+ }
+
+ if len(errs) > 0 {
+ return "", errors.Join(errs...)
+ }
+
+ return "", ErrRootlessDockerNotFound
+}
+
+func fileExists(f string) bool {
+ _, err := os.Stat(f)
+ return err == nil
+}
+
+func parseURL(s string) (string, error) {
+ hostURL, err := url.Parse(s)
+ if err != nil {
+ return "", err
+ }
+
+ switch hostURL.Scheme {
+ case "unix", "npipe":
+ return hostURL.Path, nil
+ case "tcp":
+ // return the original URL, as it is a valid TCP URL
+ return s, nil
+ default:
+ return "", ErrNoUnixSchema
+ }
+}
+
+// rootlessSocketPathFromEnv returns the path to the rootless Docker socket from the XDG_RUNTIME_DIR environment variable.
+// It should include the Docker socket schema (unix://) in the returned path.
+func rootlessSocketPathFromEnv() (string, error) {
+ xdgRuntimeDir, exists := os.LookupEnv("XDG_RUNTIME_DIR")
+ if exists {
+ f := filepath.Join(xdgRuntimeDir, "docker.sock")
+ if fileExists(f) {
+ return f, nil
+ }
+
+ return "", ErrRootlessDockerNotFoundXDGRuntimeDir
+ }
+
+ return "", ErrXDGRuntimeDirNotSet
+}
+
+// rootlessSocketPathFromHomeRunDir returns the path to the rootless Docker socket from the ~/.docker/run/docker.sock file.
+func rootlessSocketPathFromHomeRunDir() (string, error) {
+ home, err := os.UserHomeDir()
+ if err != nil {
+ return "", err
+ }
+
+ f := filepath.Join(home, ".docker", "run", "docker.sock")
+ if fileExists(f) {
+ return f, nil
+ }
+ return "", ErrRootlessDockerNotFoundHomeRunDir
+}
+
+// rootlessSocketPathFromHomeDesktopDir returns the path to the rootless Docker socket from the ~/.docker/desktop/docker.sock file.
+func rootlessSocketPathFromHomeDesktopDir() (string, error) {
+ home, err := os.UserHomeDir()
+ if err != nil {
+ return "", err
+ }
+
+ f := filepath.Join(home, ".docker", "desktop", "docker.sock")
+ if fileExists(f) {
+ return f, nil
+ }
+ return "", ErrRootlessDockerNotFoundHomeDesktopDir
+}
+
+// rootlessSocketPathFromRunDir returns the path to the rootless Docker socket from the /run/user/<uid>/docker.sock file.
+func rootlessSocketPathFromRunDir() (string, error) {
+ uid := os.Getuid()
+ f := filepath.Join(baseRunDir, "user", strconv.Itoa(uid), "docker.sock")
+ if fileExists(f) {
+ return f, nil
+ }
+ return "", ErrRootlessDockerNotFoundRunDir
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/internal/core/docker_socket.go b/vendor/github.com/testcontainers/testcontainers-go/internal/core/docker_socket.go
new file mode 100644
index 0000000..b0c0c84
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/internal/core/docker_socket.go
@@ -0,0 +1,49 @@
+package core
+
+import (
+ "net/url"
+ "strings"
+
+ "github.com/docker/docker/client"
+)
+
+// DockerSocketSchema is the unix schema.
+var DockerSocketSchema = "unix://"
+
+// DockerSocketPath is the path to the docker socket under unix systems.
+var DockerSocketPath = "/var/run/docker.sock"
+
+// DockerSocketPathWithSchema is the path to the docker socket under unix systems with the unix schema.
+var DockerSocketPathWithSchema = DockerSocketSchema + DockerSocketPath
+
+// TCPSchema is the tcp schema.
+var TCPSchema = "tcp://"
+
+// WindowsDockerSocketPath is the path to the docker socket under windows systems.
+var WindowsDockerSocketPath = "//var/run/docker.sock"
+
+func init() {
+ const DefaultDockerHost = client.DefaultDockerHost
+
+ u, err := url.Parse(DefaultDockerHost)
+ if err != nil {
+ // unsupported default host specified by the docker client package,
+ // so revert to the default unix docker socket path
+ return
+ }
+
+ switch u.Scheme {
+ case "unix", "npipe":
+ DockerSocketSchema = u.Scheme + "://"
+ DockerSocketPath = u.Path
+ if !strings.HasPrefix(DockerSocketPath, "/") {
+ // seeing as the code in this module depends on DockerSocketPath having
+ // a slash (`/`) prefix, we add it here if it is missing.
+ // for the known environments, we do not foresee how the socket-path
+ // should miss the slash, however this extra if-condition is worth to
+ // save future pain from innocent users.
+ DockerSocketPath = "/" + DockerSocketPath
+ }
+ DockerSocketPathWithSchema = DockerSocketSchema + DockerSocketPath
+ }
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/internal/core/images.go b/vendor/github.com/testcontainers/testcontainers-go/internal/core/images.go
new file mode 100644
index 0000000..f073a90
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/internal/core/images.go
@@ -0,0 +1,132 @@
+package core
+
+import (
+ "bufio"
+ "io"
+ "net/url"
+ "os"
+ "regexp"
+ "strings"
+ "unicode/utf8"
+)
+
+const (
+ IndexDockerIO = "https://index.docker.io/v1/"
+ maxURLRuneCount = 2083
+ minURLRuneCount = 3
+ URLSchema = `((ftp|tcp|udp|wss?|https?):\/\/)`
+ URLUsername = `(\S+(:\S*)?@)`
+ URLIP = `([1-9]\d?|1\d\d|2[01]\d|22[0-3]|24\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-5]))`
+ IP = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
+ URLSubdomain = `((www\.)|([a-zA-Z0-9]+([-_\.]?[a-zA-Z0-9])*[a-zA-Z0-9]\.[a-zA-Z0-9]+))`
+ URLPath = `((\/|\?|#)[^\s]*)`
+ URLPort = `(:(\d{1,5}))`
+ URL = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$`
+)
+
+var rxURL = regexp.MustCompile(URL)
+
+// ExtractImagesFromDockerfile extracts images from the Dockerfile sourced from dockerfile.
+func ExtractImagesFromDockerfile(dockerfile string, buildArgs map[string]*string) ([]string, error) {
+ file, err := os.Open(dockerfile)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ return ExtractImagesFromReader(file, buildArgs)
+}
+
+// ExtractImagesFromReader extracts images from the Dockerfile sourced from r.
+func ExtractImagesFromReader(r io.Reader, buildArgs map[string]*string) ([]string, error) {
+ var images []string
+ var lines []string
+ scanner := bufio.NewScanner(r)
+ for scanner.Scan() {
+ lines = append(lines, scanner.Text())
+ }
+ if scanner.Err() != nil {
+ return nil, scanner.Err()
+ }
+
+ // extract images from dockerfile
+ for _, line := range lines {
+ line = strings.TrimSpace(line)
+ if !strings.HasPrefix(strings.ToUpper(line), "FROM") {
+ continue
+ }
+
+ // remove FROM
+ line = strings.TrimPrefix(line, "FROM")
+ parts := strings.Split(strings.TrimSpace(line), " ")
+ if len(parts) == 0 {
+ continue
+ }
+
+ // interpolate build args
+ for k, v := range buildArgs {
+ if v != nil {
+ parts[0] = strings.ReplaceAll(parts[0], "${"+k+"}", *v)
+ }
+ }
+ images = append(images, parts[0])
+ }
+
+ return images, nil
+}
+
+// ExtractRegistry extracts the registry from the image name, using a regular expression to extract the registry from the image name.
+// regular expression to extract the registry from the image name
+// the regular expression is based on the grammar defined in
+// - image:tag
+// - image
+// - repository/image:tag
+// - repository/image
+// - registry/image:tag
+// - registry/image
+// - registry/repository/image:tag
+// - registry/repository/image
+// - registry:port/repository/image:tag
+// - registry:port/repository/image
+// - registry:port/image:tag
+// - registry:port/image
+// Once extracted the registry, it is validated to check if it is a valid URL or an IP address.
+func ExtractRegistry(image string, fallback string) string {
+ exp := regexp.MustCompile(`^(?:(?P<registry>(https?://)?[^/]+)(?::(?P<port>\d+))?/)?(?:(?P<repository>[^/]+)/)?(?P<image>[^:]+)(?::(?P<tag>.+))?$`).FindStringSubmatch(image)
+ if len(exp) == 0 {
+ return ""
+ }
+
+ registry := exp[1]
+
+ if IsURL(registry) {
+ return registry
+ }
+
+ return fallback
+}
+
+// IsURL checks if the string is a URL.
+// Extracted from https://github.com/asaskevich/govalidator/blob/f21760c49a8d/validator.go#L104
+func IsURL(str string) bool {
+ if str == "" || utf8.RuneCountInString(str) >= maxURLRuneCount || len(str) <= minURLRuneCount || strings.HasPrefix(str, ".") {
+ return false
+ }
+ strTemp := str
+ if strings.Contains(str, ":") && !strings.Contains(str, "://") {
+ // support no indicated urlscheme but with colon for port number
+ // http:// is appended so url.Parse will succeed, strTemp used so it does not impact rxURL.MatchString
+ strTemp = "http://" + str
+ }
+ u, err := url.Parse(strTemp)
+ if err != nil {
+ return false
+ }
+ if strings.HasPrefix(u.Host, ".") {
+ return false
+ }
+ if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
+ return false
+ }
+ return rxURL.MatchString(str)
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/internal/core/labels.go b/vendor/github.com/testcontainers/testcontainers-go/internal/core/labels.go
new file mode 100644
index 0000000..0814924
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/internal/core/labels.go
@@ -0,0 +1,73 @@
+package core
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/testcontainers/testcontainers-go/internal"
+ "github.com/testcontainers/testcontainers-go/internal/config"
+)
+
+const (
+ // LabelBase is the base label for all testcontainers labels.
+ LabelBase = "org.testcontainers"
+
+ // LabelLang specifies the language which created the test container.
+ LabelLang = LabelBase + ".lang"
+
+ // LabelReaper identifies the container as a reaper.
+ LabelReaper = LabelBase + ".reaper"
+
+ // LabelRyuk identifies the container as a ryuk.
+ LabelRyuk = LabelBase + ".ryuk"
+
+ // LabelSessionID specifies the session ID of the container.
+ LabelSessionID = LabelBase + ".sessionId"
+
+ // LabelVersion specifies the version of testcontainers which created the container.
+ LabelVersion = LabelBase + ".version"
+
+ // LabelReap specifies the container should be reaped by the reaper.
+ LabelReap = LabelBase + ".reap"
+)
+
+// DefaultLabels returns the standard set of labels which
+// includes LabelSessionID if the reaper is enabled.
+func DefaultLabels(sessionID string) map[string]string {
+ labels := map[string]string{
+ LabelBase: "true",
+ LabelLang: "go",
+ LabelVersion: internal.Version,
+ LabelSessionID: sessionID,
+ }
+
+ if !config.Read().RyukDisabled {
+ labels[LabelReap] = "true"
+ }
+
+ return labels
+}
+
+// AddDefaultLabels adds the default labels for sessionID to target.
+func AddDefaultLabels(sessionID string, target map[string]string) {
+ for k, v := range DefaultLabels(sessionID) {
+ target[k] = v
+ }
+}
+
+// MergeCustomLabels sets labels from src to dst.
+// If a key in src has [LabelBase] prefix returns an error.
+// If dst is nil returns an error.
+func MergeCustomLabels(dst, src map[string]string) error {
+ if dst == nil {
+ return errors.New("destination map is nil")
+ }
+ for key, value := range src {
+ if strings.HasPrefix(key, LabelBase) {
+ return fmt.Errorf("key %q has %q prefix", key, LabelBase)
+ }
+ dst[key] = value
+ }
+ return nil
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/internal/core/network/network.go b/vendor/github.com/testcontainers/testcontainers-go/internal/core/network/network.go
new file mode 100644
index 0000000..787065a
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/internal/core/network/network.go
@@ -0,0 +1,52 @@
+package network
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/docker/docker/api/types/filters"
+ "github.com/docker/docker/api/types/network"
+
+ "github.com/testcontainers/testcontainers-go/internal/core"
+)
+
+const (
+ // FilterByID uses to filter network by identifier.
+ FilterByID = "id"
+
+ // FilterByName uses to filter network by name.
+ FilterByName = "name"
+)
+
+// Get returns a network by its ID.
+func Get(ctx context.Context, id string) (network.Inspect, error) {
+ return get(ctx, FilterByID, id)
+}
+
+// GetByName returns a network by its name.
+func GetByName(ctx context.Context, name string) (network.Inspect, error) {
+ return get(ctx, FilterByName, name)
+}
+
+func get(ctx context.Context, filter string, value string) (network.Inspect, error) {
+ var nw network.Inspect // initialize to the zero value
+
+ cli, err := core.NewClient(ctx)
+ if err != nil {
+ return nw, err
+ }
+ defer cli.Close()
+
+ list, err := cli.NetworkList(ctx, network.ListOptions{
+ Filters: filters.NewArgs(filters.Arg(filter, value)),
+ })
+ if err != nil {
+ return nw, fmt.Errorf("failed to list networks: %w", err)
+ }
+
+ if len(list) == 0 {
+ return nw, fmt.Errorf("network %s not found (filtering by %s)", value, filter)
+ }
+
+ return list[0], nil
+}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/internal/version.go b/vendor/github.com/testcontainers/testcontainers-go/internal/version.go
new file mode 100644
index 0000000..7e6da64
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/internal/version.go
@@ -0,0 +1,4 @@
+package internal
+
+// Version is the next development version of the application
+const Version = "0.36.0"