diff --git a/internal/core/client.go b/internal/core/client.go index 04a54bcbc5..cccc7352fa 100644 --- a/internal/core/client.go +++ b/internal/core/client.go @@ -2,6 +2,7 @@ package core import ( "context" + "fmt" "path/filepath" "github.com/docker/docker/client" @@ -14,7 +15,10 @@ import ( func NewClient(ctx context.Context, ops ...client.Opt) (*client.Client, error) { tcConfig := config.Read() - dockerHost := MustExtractDockerHost(ctx) + dockerHost, err := extractDockerHost(ctx) + if err != nil { + return nil, fmt.Errorf("extract docker host: %w", err) + } opts := []client.Opt{client.FromEnv, client.WithAPIVersionNegotiation()} if dockerHost != "" { diff --git a/internal/core/docker_host.go b/internal/core/docker_host.go index fc06ea8ad1..3d47923810 100644 --- a/internal/core/docker_host.go +++ b/internal/core/docker_host.go @@ -28,6 +28,7 @@ var ( 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") + ErrDockerSocketNotSetInDockerContext = errors.New("socket not found in docker context") ) var ( @@ -73,7 +74,7 @@ var dockerHostCheck = func(ctx context.Context, host string) error { return nil } -// MustExtractDockerHost Extracts the docker host from the different alternatives, caching the result to avoid unnecessary +// 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: // @@ -118,13 +119,26 @@ func MustExtractDockerSocket(ctx context.Context) string { return dockerSocketPathCache } -// extractDockerHost Extracts the docker host from the different alternatives, without caching the result. -// This internal method is handy for testing purposes. +// ExtractDockerHost Extracts the docker host from the different alternatives, without caching the result. +// 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. +func ExtractDockerHost(ctx context.Context) (string, error) { + return extractDockerHost(ctx) +} + func extractDockerHost(ctx context.Context) (string, error) { dockerHostFns := []func(context.Context) (string, error){ testcontainersHostFromProperties, dockerHostFromEnv, dockerHostFromContext, + dockerHostFromDockerContext, dockerSocketPath, dockerHostFromProperties, rootlessDockerSocketPath, @@ -227,12 +241,14 @@ func isHostNotSet(err error) bool { case errors.Is(err, ErrTestcontainersHostNotSetInProperties), errors.Is(err, ErrDockerHostNotSet), errors.Is(err, ErrDockerSocketNotSetInContext), + errors.Is(err, ErrDockerSocketNotSetInDockerContext), errors.Is(err, ErrDockerSocketNotSetInProperties), errors.Is(err, ErrSocketNotFoundInPath), errors.Is(err, ErrXDGRuntimeDirNotSet), errors.Is(err, ErrRootlessDockerNotFoundHomeRunDir), errors.Is(err, ErrRootlessDockerNotFoundHomeDesktopDir), - errors.Is(err, ErrRootlessDockerNotFoundRunDir): + errors.Is(err, ErrRootlessDockerNotFoundRunDir), + errors.Is(err, ErrRootlessDockerNotFound): return true default: return false @@ -262,6 +278,17 @@ func dockerHostFromContext(ctx context.Context) (string, error) { return "", ErrDockerSocketNotSetInContext } +// dockerHostFromDockerContext returns the docker host from the DOCKER_CONTEXT environment variable, if it's not empty +func dockerHostFromDockerContext(_ context.Context) (string, error) { + // exec `docker context inspect -f='{{.Endpoints.docker.Host}}'` + cmd := exec.Command("docker", "context", "inspect", "-f", "{{.Endpoints.docker.Host}}") + output, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("%w: %w", ErrDockerSocketNotSetInDockerContext, err) + } + return strings.TrimSpace(string(output)), nil +} + // dockerHostFromProperties returns the docker host from the ~/.testcontainers.properties file, if it's not empty func dockerHostFromProperties(_ context.Context) (string, error) { cfg := config.Read() diff --git a/provider.go b/provider.go index d2347b7f3b..2442f924a6 100644 --- a/provider.go +++ b/provider.go @@ -146,9 +146,13 @@ func NewDockerProvider(provOpts ...DockerProviderOption) (*DockerProvider, error return nil, err } + host, err := core.ExtractDockerHost(ctx) + if err != nil { + return nil, fmt.Errorf("failed to extract docker host: %w", err) + } return &DockerProvider{ DockerProviderOptions: o, - host: core.MustExtractDockerHost(ctx), + host: host, client: c, config: config.Read(), }, nil