Skip to content

Commit 1f31bb2

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 00f2020 commit 1f31bb2

4 files changed

Lines changed: 184 additions & 7 deletions

File tree

internal/config/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ type Config struct {
8585
//
8686
// Environment variable: TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE
8787
TestcontainersHost string `properties:"tc.host,default="`
88+
89+
// Provider is the container provider to use (e.g., "docker", "podman").
90+
//
91+
// Environment variable: TESTCONTAINERS_PROVIDER
92+
Provider string `properties:"provider,default="`
8893
}
8994

9095
// }
@@ -141,6 +146,11 @@ func read() Config {
141146
config.RyukConnectionTimeout = timeout
142147
}
143148

149+
providerEnv := os.Getenv("TESTCONTAINERS_PROVIDER")
150+
if providerEnv != "" {
151+
config.Provider = providerEnv
152+
}
153+
144154
return config
145155
}
146156

internal/config/config_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func resetTestEnv(t *testing.T) {
2626
t.Setenv("RYUK_VERBOSE", "")
2727
t.Setenv("RYUK_RECONNECTION_TIMEOUT", "")
2828
t.Setenv("RYUK_CONNECTION_TIMEOUT", "")
29+
t.Setenv("TESTCONTAINERS_PROVIDER", "")
2930
}
3031

3132
func TestReadConfig(t *testing.T) {
@@ -38,12 +39,14 @@ func TestReadConfig(t *testing.T) {
3839
t.Setenv("USERPROFILE", "") // Windows support
3940
t.Setenv("DOCKER_HOST", "")
4041
t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true")
42+
t.Setenv("TESTCONTAINERS_PROVIDER", "podman")
4143

4244
config := Read()
4345

4446
expected := Config{
4547
RyukDisabled: true,
4648
Host: "", // docker socket is empty at the properties file
49+
Provider: "podman",
4750
}
4851

4952
require.Equal(t, expected, config)
@@ -79,6 +82,7 @@ func TestReadTCConfig(t *testing.T) {
7982
t.Setenv("TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED", "true")
8083
t.Setenv("RYUK_RECONNECTION_TIMEOUT", "13s")
8184
t.Setenv("RYUK_CONNECTION_TIMEOUT", "12s")
85+
t.Setenv("TESTCONTAINERS_PROVIDER", "docker")
8286

8387
config := read()
8488

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

9499
assert.Equal(t, expected, config)
@@ -516,6 +521,40 @@ func TestReadTCConfig(t *testing.T) {
516521
RyukReconnectionTimeout: defaultRyukReconnectionTimeout,
517522
},
518523
},
524+
{
525+
"With Provider set as property",
526+
`provider=podman`,
527+
map[string]string{},
528+
Config{
529+
Provider: "podman",
530+
RyukConnectionTimeout: defaultRyukConnectionTimeout,
531+
RyukReconnectionTimeout: defaultRyukReconnectionTimeout,
532+
},
533+
},
534+
{
535+
"With Provider set as env var",
536+
``,
537+
map[string]string{
538+
"TESTCONTAINERS_PROVIDER": "podman",
539+
},
540+
Config{
541+
Provider: "podman",
542+
RyukConnectionTimeout: defaultRyukConnectionTimeout,
543+
RyukReconnectionTimeout: defaultRyukReconnectionTimeout,
544+
},
545+
},
546+
{
547+
"With Provider set as env var and properties: Env var wins",
548+
`provider=docker`,
549+
map[string]string{
550+
"TESTCONTAINERS_PROVIDER": "podman",
551+
},
552+
Config{
553+
Provider: "podman",
554+
RyukConnectionTimeout: defaultRyukConnectionTimeout,
555+
RyukReconnectionTimeout: defaultRyukReconnectionTimeout,
556+
},
557+
},
519558
}
520559
for _, tt := range tests {
521560
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 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: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,123 @@ 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+
100+
// Set any additional environment variables
101+
for k, v := range tt.env {
102+
t.Setenv(k, v)
103+
}
104+
105+
// Create properties file if content is provided
106+
if tt.propertiesFile != "" {
107+
err := os.WriteFile(
108+
filepath.Join(tmpDir, ".testcontainers.properties"),
109+
[]byte(tt.propertiesFile),
110+
0600,
111+
)
112+
require.NoError(t, err, "Failed to create properties file")
113+
}
114+
115+
// Test UnderlyingProviderType
116+
result := tt.providerType.UnderlyingProviderType()
117+
require.Equal(t, tt.expectedType, result, "UnderlyingProviderType() returned unexpected type")
118+
})
119+
}
120+
}
121+
12122
func TestProviderTypeGetProviderAutodetect(t *testing.T) {
13123
dockerHost := core.MustExtractDockerHost(context.Background())
14124
const podmanSocket = "unix://$XDG_RUNTIME_DIR/podman/podman.sock"

0 commit comments

Comments
 (0)