Skip to content

Commit 1ddf78c

Browse files
committed
Add the ability to configure the provider type in the properties file or as an env var
Using WithProvider may not be portable when some developers running tests may be using Podman and others using Docker. Allowing setting the Provider type in the global configuration or environment ensures that the provider is a per-developer configurable setting.
1 parent 3862405 commit 1ddf78c

5 files changed

Lines changed: 187 additions & 7 deletions

File tree

‎docs/system_requirements/using_podman.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ In order to use Testcontainers then either
8282
}
8383
```
8484

85+
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.
86+
8587
## Fedora
8688

8789
`DOCKER_HOST` environment variable must be set

‎internal/config/config.go‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ type Config struct {
131131
//
132132
// Environment variable: TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE
133133
TestcontainersHost string `properties:"tc.host,default="`
134+
135+
// Provider is the container provider to use (e.g., "docker", "podman").
136+
//
137+
// Environment variable: TESTCONTAINERS_PROVIDER
138+
Provider string `properties:"provider,default="`
134139
}
135140

136141
// }
@@ -202,6 +207,11 @@ func read() Config {
202207
config.RyukConnectionTimeout = timeout
203208
}
204209

210+
providerEnv := os.Getenv("TESTCONTAINERS_PROVIDER")
211+
if providerEnv != "" {
212+
config.Provider = providerEnv
213+
}
214+
205215
return config
206216
}
207217

‎internal/config/config_test.go‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func resetTestEnv(t *testing.T) {
3030
t.Setenv("RYUK_VERBOSE", "")
3131
t.Setenv("RYUK_RECONNECTION_TIMEOUT", "")
3232
t.Setenv("RYUK_CONNECTION_TIMEOUT", "")
33+
t.Setenv("TESTCONTAINERS_PROVIDER", "")
3334
}
3435

3536
func TestReadConfig(t *testing.T) {
@@ -42,13 +43,15 @@ func TestReadConfig(t *testing.T) {
4243
t.Setenv("USERPROFILE", "") // Windows support
4344
t.Setenv("DOCKER_HOST", "")
4445
t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true")
46+
t.Setenv("TESTCONTAINERS_PROVIDER", "podman")
4547

4648
config := Read()
4749

4850
expected := Config{
4951
SessionID: bootstrap.SessionID(),
5052
RyukDisabled: true,
5153
Host: "", // docker socket is empty at the properties file
54+
Provider: "podman",
5255
}
5356

5457
require.Equal(t, expected, config)
@@ -87,6 +90,7 @@ func TestReadTCConfig(t *testing.T) {
8790
t.Setenv("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED", "true")
8891
t.Setenv("RYUK_RECONNECTION_TIMEOUT", "13s")
8992
t.Setenv("RYUK_CONNECTION_TIMEOUT", "12s")
93+
t.Setenv("TESTCONTAINERS_PROVIDER", "docker")
9094

9195
config := read()
9296

@@ -98,6 +102,7 @@ func TestReadTCConfig(t *testing.T) {
98102
Host: "", // docker socket is empty at the properties file
99103
RyukReconnectionTimeout: 13 * time.Second,
100104
RyukConnectionTimeout: 12 * time.Second,
105+
Provider: "docker",
101106
}
102107

103108
assert.Equal(t, expected, config)
@@ -569,6 +574,16 @@ func TestReadTCConfig(t *testing.T) {
569574
RyukReconnectionTimeout: defaultRyukReconnectionTimeout,
570575
},
571576
},
577+
{
578+
"With Provider set as property",
579+
`provider=podman`,
580+
map[string]string{},
581+
Config{
582+
Provider: "podman",
583+
RyukConnectionTimeout: defaultRyukConnectionTimeout,
584+
RyukReconnectionTimeout: defaultRyukReconnectionTimeout,
585+
},
586+
},
572587
{
573588
"With Session ID set using an env var and properties. Env var wins",
574589
`session.id=bar`,
@@ -581,6 +596,30 @@ func TestReadTCConfig(t *testing.T) {
581596
RyukReconnectionTimeout: defaultRyukReconnectionTimeout,
582597
},
583598
},
599+
{
600+
"With Provider set as env var",
601+
``,
602+
map[string]string{
603+
"TESTCONTAINERS_PROVIDER": "podman",
604+
},
605+
Config{
606+
Provider: "podman",
607+
RyukConnectionTimeout: defaultRyukConnectionTimeout,
608+
RyukReconnectionTimeout: defaultRyukReconnectionTimeout,
609+
},
610+
},
611+
{
612+
"With Provider set as env var and properties: Env var wins",
613+
`provider=docker`,
614+
map[string]string{
615+
"TESTCONTAINERS_PROVIDER": "podman",
616+
},
617+
Config{
618+
Provider: "podman",
619+
RyukConnectionTimeout: defaultRyukConnectionTimeout,
620+
RyukReconnectionTimeout: defaultRyukReconnectionTimeout,
621+
},
622+
},
584623
}
585624
for _, tt := range tests {
586625
t.Run(tt.name, func(t *testing.T) {

‎provider.go‎

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"os"
87
"strings"
98

109
"github.com/testcontainers/testcontainers-go/internal/config"
@@ -94,6 +93,30 @@ type ContainerProvider interface {
9493
Config() TestcontainersConfig
9594
}
9695

96+
func (t ProviderType) UnderlyingProviderType() ProviderType {
97+
// Provider set within code has precedence over all others
98+
if t != ProviderDefault {
99+
return t
100+
}
101+
102+
// Configuration of an explicit provider has the next priority
103+
conf := config.Read()
104+
switch strings.ToLower(conf.Provider) {
105+
case "docker":
106+
return ProviderDocker
107+
case "podman":
108+
return ProviderPodman
109+
}
110+
111+
// Attempt to auto-detect Podman from the the Docker configuration
112+
if strings.Contains(core.MustExtractDockerHost(context.Background()), "podman.sock") {
113+
return ProviderPodman
114+
}
115+
116+
// When all else fails, default to Docker
117+
return ProviderDocker
118+
}
119+
97120
// GetProvider provides the provider implementation for a certain type
98121
func (t ProviderType) GetProvider(opts ...GenericProviderOption) (GenericProvider, error) {
99122
opt := &GenericProviderOptions{
@@ -104,12 +127,7 @@ func (t ProviderType) GetProvider(opts ...GenericProviderOption) (GenericProvide
104127
o.ApplyGenericTo(opt)
105128
}
106129

107-
pt := t
108-
if pt == ProviderDefault && strings.Contains(os.Getenv("DOCKER_HOST"), "podman.sock") {
109-
pt = ProviderPodman
110-
}
111-
112-
switch pt {
130+
switch t.UnderlyingProviderType() {
113131
case ProviderDefault, ProviderDocker:
114132
providerOptions := append(Generic2DockerOptions(opts...), WithDefaultBridgeNetwork(Bridge))
115133
provider, err := NewDockerProvider(providerOptions...)

‎provider_test.go‎

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,124 @@ package testcontainers
22

33
import (
44
"context"
5+
"os"
6+
"path/filepath"
57
"testing"
68

79
"github.com/stretchr/testify/require"
810

11+
"github.com/testcontainers/testcontainers-go/internal/config"
912
"github.com/testcontainers/testcontainers-go/internal/core"
1013
)
1114

15+
func TestProviderTypeGetUnderlyingProviderType(t *testing.T) {
16+
tests := []struct {
17+
name string
18+
providerType ProviderType
19+
propertiesFile string // content of .testcontainers.properties
20+
env map[string]string
21+
expectedType ProviderType
22+
}{
23+
{
24+
name: "ProviderDocker always returns ProviderDocker",
25+
providerType: ProviderDocker,
26+
expectedType: ProviderDocker,
27+
},
28+
{
29+
name: "ProviderPodman always returns ProviderPodman",
30+
providerType: ProviderPodman,
31+
expectedType: ProviderPodman,
32+
},
33+
{
34+
name: "ProviderDefault with properties file set to docker",
35+
providerType: ProviderDefault,
36+
propertiesFile: "provider=docker",
37+
expectedType: ProviderDocker,
38+
},
39+
{
40+
name: "ProviderDefault with properties file set to podman",
41+
providerType: ProviderDefault,
42+
propertiesFile: "provider=podman",
43+
expectedType: ProviderPodman,
44+
},
45+
{
46+
name: "ProviderDefault with env var set to docker",
47+
providerType: ProviderDefault,
48+
env: map[string]string{
49+
"TESTCONTAINERS_PROVIDER": "docker",
50+
},
51+
expectedType: ProviderDocker,
52+
},
53+
{
54+
name: "ProviderDefault with env var set to podman",
55+
providerType: ProviderDefault,
56+
env: map[string]string{
57+
"TESTCONTAINERS_PROVIDER": "podman",
58+
},
59+
expectedType: ProviderPodman,
60+
},
61+
{
62+
name: "ProviderDefault with env var podman and properties docker - env wins",
63+
providerType: ProviderDefault,
64+
propertiesFile: "provider=docker",
65+
env: map[string]string{
66+
"TESTCONTAINERS_PROVIDER": "podman",
67+
},
68+
expectedType: ProviderPodman,
69+
},
70+
{
71+
name: "ProviderDocker with env var podman and properties podman - explicit provider wins",
72+
providerType: ProviderDocker,
73+
propertiesFile: "provider=podman",
74+
env: map[string]string{
75+
"TESTCONTAINERS_PROVIDER": "podman",
76+
},
77+
expectedType: ProviderDocker,
78+
},
79+
{
80+
name: "ProviderPodman with env var docker and properties docker - explicit provider wins",
81+
providerType: ProviderPodman,
82+
propertiesFile: "provider=docker",
83+
env: map[string]string{
84+
"TESTCONTAINERS_PROVIDER": "docker",
85+
},
86+
expectedType: ProviderPodman,
87+
},
88+
}
89+
90+
for _, tt := range tests {
91+
t.Run(tt.name, func(t *testing.T) {
92+
// Reset config for each test to ensure clean state
93+
config.Reset()
94+
95+
// Create temp directory for HOME
96+
tmpDir := t.TempDir()
97+
t.Setenv("HOME", tmpDir)
98+
t.Setenv("USERPROFILE", tmpDir) // Windows support
99+
t.Setenv("TESTCONTAINERS_PROVIDER", "") // Ensure env var is not set before test
100+
101+
// Set any additional environment variables
102+
for k, v := range tt.env {
103+
t.Setenv(k, v)
104+
}
105+
106+
// Create properties file if content is provided
107+
if tt.propertiesFile != "" {
108+
err := os.WriteFile(
109+
filepath.Join(tmpDir, ".testcontainers.properties"),
110+
[]byte(tt.propertiesFile),
111+
0o600,
112+
)
113+
require.NoError(t, err, "Failed to create properties file")
114+
}
115+
116+
// Test UnderlyingProviderType
117+
result := tt.providerType.UnderlyingProviderType()
118+
require.Equal(t, tt.expectedType, result, "UnderlyingProviderType() returned unexpected type")
119+
})
120+
}
121+
}
122+
12123
func TestProviderTypeGetProviderAutodetect(t *testing.T) {
13124
dockerHost := core.MustExtractDockerHost(context.Background())
14125
const podmanSocket = "unix://$XDG_RUNTIME_DIR/podman/podman.sock"

0 commit comments

Comments
 (0)