Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/system_requirements/using_podman.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ In order to use Testcontainers then either
}
```

As an alternative to declaring the provider in the tests themselves, you can instead set the `TESTCONTAINERS_PROVIDER` environment variable to `podman` to force all tests to execute with the Podman provider. This may also be set in the .testcontainers.properties file as `provider=podman` in your home directory.

## Fedora

`DOCKER_HOST` environment variable must be set
Expand Down
10 changes: 10 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ type Config struct {
//
// Environment variable: TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE
TestcontainersHost string `properties:"tc.host,default="`

// Provider is the container provider to use (e.g., "docker", "podman").
//
// Environment variable: TESTCONTAINERS_PROVIDER
Provider string `properties:"provider,default="`
}

// }
Expand Down Expand Up @@ -141,6 +146,11 @@ func read() Config {
config.RyukConnectionTimeout = timeout
}

providerEnv := os.Getenv("TESTCONTAINERS_PROVIDER")
if providerEnv != "" {
config.Provider = providerEnv
}

return config
}

Expand Down
39 changes: 39 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func resetTestEnv(t *testing.T) {
t.Setenv("RYUK_VERBOSE", "")
t.Setenv("RYUK_RECONNECTION_TIMEOUT", "")
t.Setenv("RYUK_CONNECTION_TIMEOUT", "")
t.Setenv("TESTCONTAINERS_PROVIDER", "")
}

func TestReadConfig(t *testing.T) {
Expand All @@ -38,12 +39,14 @@ func TestReadConfig(t *testing.T) {
t.Setenv("USERPROFILE", "") // Windows support
t.Setenv("DOCKER_HOST", "")
t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true")
t.Setenv("TESTCONTAINERS_PROVIDER", "podman")

config := Read()

expected := Config{
RyukDisabled: true,
Host: "", // docker socket is empty at the properties file
Provider: "podman",
}

require.Equal(t, expected, config)
Expand Down Expand Up @@ -79,6 +82,7 @@ func TestReadTCConfig(t *testing.T) {
t.Setenv("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED", "true")
t.Setenv("RYUK_RECONNECTION_TIMEOUT", "13s")
t.Setenv("RYUK_CONNECTION_TIMEOUT", "12s")
t.Setenv("TESTCONTAINERS_PROVIDER", "docker")

config := read()

Expand All @@ -89,6 +93,7 @@ func TestReadTCConfig(t *testing.T) {
Host: "", // docker socket is empty at the properties file
RyukReconnectionTimeout: 13 * time.Second,
RyukConnectionTimeout: 12 * time.Second,
Provider: "docker",
}

assert.Equal(t, expected, config)
Expand Down Expand Up @@ -516,6 +521,40 @@ func TestReadTCConfig(t *testing.T) {
RyukReconnectionTimeout: defaultRyukReconnectionTimeout,
},
},
{
"With Provider set as property",
`provider=podman`,
map[string]string{},
Config{
Provider: "podman",
RyukConnectionTimeout: defaultRyukConnectionTimeout,
RyukReconnectionTimeout: defaultRyukReconnectionTimeout,
},
},
{
"With Provider set as env var",
``,
map[string]string{
"TESTCONTAINERS_PROVIDER": "podman",
},
Config{
Provider: "podman",
RyukConnectionTimeout: defaultRyukConnectionTimeout,
RyukReconnectionTimeout: defaultRyukReconnectionTimeout,
},
},
{
"With Provider set as env var and properties: Env var wins",
`provider=docker`,
map[string]string{
"TESTCONTAINERS_PROVIDER": "podman",
},
Config{
Provider: "podman",
RyukConnectionTimeout: defaultRyukConnectionTimeout,
RyukReconnectionTimeout: defaultRyukReconnectionTimeout,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
32 changes: 25 additions & 7 deletions provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"os"
"strings"

"github.com/testcontainers/testcontainers-go/internal/config"
Expand Down Expand Up @@ -94,6 +93,30 @@ type ContainerProvider interface {
Config() TestcontainersConfig
}

func (t ProviderType) UnderlyingProviderType() ProviderType {
// Provider set within code has precedence over all others
if t != ProviderDefault {
return t
}

// Configuration of an explicit provider has the next priority
conf := config.Read()
switch strings.ToLower(conf.Provider) {
case "docker":
return ProviderDocker
case "podman":
return ProviderPodman
}

// Attempt to auto-detect Podman from the the Docker configuration
if strings.Contains(core.MustExtractDockerHost(context.Background()), "podman.sock") {
return ProviderPodman
}

// When all else fails, default to Docker
return ProviderDocker
}
Comment thread
mdelapenya marked this conversation as resolved.

// GetProvider provides the provider implementation for a certain type
func (t ProviderType) GetProvider(opts ...GenericProviderOption) (GenericProvider, error) {
opt := &GenericProviderOptions{
Expand All @@ -104,12 +127,7 @@ 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
}

switch pt {
switch t.UnderlyingProviderType() {
case ProviderDefault, ProviderDocker:
providerOptions := append(Generic2DockerOptions(opts...), WithDefaultBridgeNetwork(Bridge))
provider, err := NewDockerProvider(providerOptions...)
Expand Down
111 changes: 111 additions & 0 deletions provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,124 @@ package testcontainers

import (
"context"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"

"github.com/testcontainers/testcontainers-go/internal/config"
"github.com/testcontainers/testcontainers-go/internal/core"
)

func TestProviderTypeGetUnderlyingProviderType(t *testing.T) {
tests := []struct {
name string
providerType ProviderType
propertiesFile string // content of .testcontainers.properties
env map[string]string
expectedType ProviderType
}{
{
name: "ProviderDocker always returns ProviderDocker",
providerType: ProviderDocker,
expectedType: ProviderDocker,
},
{
name: "ProviderPodman always returns ProviderPodman",
providerType: ProviderPodman,
expectedType: ProviderPodman,
},
{
name: "ProviderDefault with properties file set to docker",
providerType: ProviderDefault,
propertiesFile: "provider=docker",
expectedType: ProviderDocker,
},
{
name: "ProviderDefault with properties file set to podman",
providerType: ProviderDefault,
propertiesFile: "provider=podman",
expectedType: ProviderPodman,
},
{
name: "ProviderDefault with env var set to docker",
providerType: ProviderDefault,
env: map[string]string{
"TESTCONTAINERS_PROVIDER": "docker",
},
expectedType: ProviderDocker,
},
{
name: "ProviderDefault with env var set to podman",
providerType: ProviderDefault,
env: map[string]string{
"TESTCONTAINERS_PROVIDER": "podman",
},
expectedType: ProviderPodman,
},
{
name: "ProviderDefault with env var podman and properties docker - env wins",
providerType: ProviderDefault,
propertiesFile: "provider=docker",
env: map[string]string{
"TESTCONTAINERS_PROVIDER": "podman",
},
expectedType: ProviderPodman,
},
{
name: "ProviderDocker with env var podman and properties podman - explicit provider wins",
providerType: ProviderDocker,
propertiesFile: "provider=podman",
env: map[string]string{
"TESTCONTAINERS_PROVIDER": "podman",
},
expectedType: ProviderDocker,
},
{
name: "ProviderPodman with env var docker and properties docker - explicit provider wins",
providerType: ProviderPodman,
propertiesFile: "provider=docker",
env: map[string]string{
"TESTCONTAINERS_PROVIDER": "docker",
},
expectedType: ProviderPodman,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Reset config for each test to ensure clean state
config.Reset()

// Create temp directory for HOME
tmpDir := t.TempDir()
t.Setenv("HOME", tmpDir)
t.Setenv("USERPROFILE", tmpDir) // Windows support
t.Setenv("TESTCONTAINERS_PROVIDER", "") // Ensure env var is not set before test

// Set any additional environment variables
for k, v := range tt.env {
t.Setenv(k, v)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Create properties file if content is provided
if tt.propertiesFile != "" {
err := os.WriteFile(
filepath.Join(tmpDir, ".testcontainers.properties"),
[]byte(tt.propertiesFile),
0o600,
)
require.NoError(t, err, "Failed to create properties file")
}

// Test UnderlyingProviderType
result := tt.providerType.UnderlyingProviderType()
require.Equal(t, tt.expectedType, result, "UnderlyingProviderType() returned unexpected type")
})
}
}

func TestProviderTypeGetProviderAutodetect(t *testing.T) {
dockerHost := core.MustExtractDockerHost(context.Background())
const podmanSocket = "unix://$XDG_RUNTIME_DIR/podman/podman.sock"
Expand Down
Loading