diff --git a/docs/system_requirements/using_podman.md b/docs/system_requirements/using_podman.md index fae9731a65..046dbb1c11 100644 --- a/docs/system_requirements/using_podman.md +++ b/docs/system_requirements/using_podman.md @@ -35,6 +35,15 @@ func TestSomething(t *testing.T) { } ``` +If using the [Podman Desktop Docker compatibility mode](https://podman-desktop.io/docs/migrating-from-docker/using-podman-mac-helper), +where the `DOCKER_HOST` still points to the Docker socket, +you can also configure the provider explicitly in a `.testcontainers.properties` file +or the `TESTCONTAINERS_PROVIDER_TYPE` env variable. +```properties +tc.provider=podman +``` + + The `ProviderPodman` configures the `DockerProvider` with the correct default network for Podman to ensure complex network scenarios are working as with Docker. ## Podman socket activation diff --git a/internal/config/config.go b/internal/config/config.go index 038a7b85d8..e6b7a84ca2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -29,6 +29,7 @@ type Config struct { RyukPrivileged bool `properties:"ryuk.container.privileged,default=false"` RyukReconnectionTimeout time.Duration `properties:"ryuk.reconnection.timeout,default=10s"` RyukConnectionTimeout time.Duration `properties:"ryuk.connection.timeout,default=1m"` + ProviderType string `properties:"provider.type,default="` TestcontainersHost string `properties:"tc.host,default="` } @@ -80,6 +81,8 @@ func read() Config { config.RyukPrivileged = ryukPrivilegedEnv == "true" } + config.ProviderType = os.Getenv("TESTCONTAINERS_PROVIDER_TYPE") + return config } diff --git a/provider.go b/provider.go index 84e959d9e0..df01e4e398 100644 --- a/provider.go +++ b/provider.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/testcontainers/testcontainers-go/internal/config" "os" "strings" @@ -102,12 +103,21 @@ func (t ProviderType) GetProvider(opts ...GenericProviderOption) (GenericProvide o.ApplyGenericTo(opt) } - pt := t - if pt == ProviderDefault && strings.Contains(os.Getenv("DOCKER_HOST"), "podman.sock") { - pt = ProviderPodman + providerType := t + if providerType == ProviderDefault { + cfg := config.Read() + var err error + providerType, err = getProviderType(cfg.ProviderType) + if err != nil { + return nil, err + } + } + + if providerType == ProviderDefault && strings.Contains(os.Getenv("DOCKER_HOST"), "podman.sock") { + providerType = ProviderPodman } - switch pt { + switch providerType { case ProviderDefault, ProviderDocker: providerOptions := append(Generic2DockerOptions(opts...), WithDefaultBridgeNetwork(Bridge)) provider, err := NewDockerProvider(providerOptions...) @@ -157,3 +167,16 @@ func NewDockerProvider(provOpts ...DockerProviderOption) (*DockerProvider, error return p, nil } + +// getProviderType maps a human-readable string to a corresponding ProviderType. +func getProviderType(providerType string) (ProviderType, error) { + switch providerType { + case "": + return ProviderDefault, nil + case "docker": + return ProviderDocker, nil + case "podman": + return ProviderPodman, nil + } + return 0, errors.New(fmt.Sprintf("unknown provider: %s", providerType)) +} diff --git a/provider_test.go b/provider_test.go index 3459dfe634..1a6fdb8834 100644 --- a/provider_test.go +++ b/provider_test.go @@ -2,6 +2,9 @@ package testcontainers import ( "context" + "github.com/testcontainers/testcontainers-go/internal/config" + "os" + "path/filepath" "testing" "github.com/testcontainers/testcontainers-go/internal/testcontainersdocker" @@ -12,11 +15,12 @@ func TestProviderTypeGetProviderAutodetect(t *testing.T) { const podmanSocket = "unix://$XDG_RUNTIME_DIR/podman/podman.sock" tests := []struct { - name string - tr ProviderType - DockerHost string - want string - wantErr bool + name string + tr ProviderType + PropertiesProvider string + DockerHost string + want string + wantErr bool }{ { name: "default provider without podman.socket", @@ -55,6 +59,21 @@ func TestProviderTypeGetProviderAutodetect(t *testing.T) { DockerHost: podmanSocket, want: Podman, }, + { + name: "default provider with podman configured in properties", + tr: ProviderDefault, + PropertiesProvider: "podman", + DockerHost: dockerHost, + want: Podman, + }, + { + // Explicitly setting Docker provider should not be overridden by properties + name: "docker provider with podman configured in properties", + tr: ProviderDocker, + PropertiesProvider: "podman", + DockerHost: dockerHost, + want: Bridge, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -64,6 +83,8 @@ func TestProviderTypeGetProviderAutodetect(t *testing.T) { t.Setenv("DOCKER_HOST", tt.DockerHost) + setupTestcontainersProperties(t, "provider.type="+tt.PropertiesProvider) + got, err := tt.tr.GetProvider() if (err != nil) != tt.wantErr { t.Errorf("ProviderType.GetProvider() error = %v, wantErr %v", err, tt.wantErr) @@ -79,3 +100,35 @@ func TestProviderTypeGetProviderAutodetect(t *testing.T) { }) } } + +func setupTestcontainersProperties(t *testing.T, content string) { + t.Cleanup(func() { + // reset the properties file after the test + config.Reset() + }) + + config.Reset() + + tmpDir := t.TempDir() + homeDir := filepath.Join(tmpDir, "home") + err := createTmpDir(homeDir) + if err != nil { + t.Fatalf("failed to create tmp home dir: %v", err) + } + t.Setenv("HOME", homeDir) + t.Setenv("USERPROFILE", homeDir) // Windows support + + if err := os.WriteFile(filepath.Join(homeDir, ".testcontainers.properties"), []byte(content), 0o600); err != nil { + t.Errorf("Failed to create the file: %v", err) + return + } +} + +func createTmpDir(dir string) error { + err := os.MkdirAll(dir, 0o755) + if err != nil { + return err + } + + return nil +}