diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..dfdb8b771c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/.github/workflows/ci-test-go.yml b/.github/workflows/ci-test-go.yml index a56dab64a4..56633716d4 100644 --- a/.github/workflows/ci-test-go.yml +++ b/.github/workflows/ci-test-go.yml @@ -37,6 +37,9 @@ on: type: boolean default: false description: "Disable the ryuk container for the test." + secrets: + TC_CLOUD_TOKEN: + required: true permissions: contents: read @@ -92,15 +95,18 @@ jobs: working-directory: ./${{ inputs.project-directory }} run: make tools-tidy + - name: Setup Testcontainers Cloud Client + # Use Testcontainers Cloud for Windows and MacOS, but only for merge commits on main + if: ${{ inputs.run-tests && (inputs.platform == 'windows-latest' || inputs.platform == 'macos-latest') }} + uses: atomicjar/testcontainers-cloud-setup-action@v1 + with: + token: ${{ secrets.TC_CLOUD_TOKEN }} + - name: ensure compilation working-directory: ./${{ inputs.project-directory }} run: go build - name: go test - # only run tests on linux, there are a number of things that won't allow the tests to run on anything else - # many (maybe, all?) images used can only be build on Linux, they don't have Windows in their manifest, and - # we can't put Windows Server in "Linux Mode" in Github actions - # another, host mode is only available on Linux, and we have tests around that, do we skip them? if: ${{ inputs.run-tests }} working-directory: ./${{ inputs.project-directory }} timeout-minutes: 30 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ee4391d22..4eeb873e75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,15 +27,16 @@ jobs: strategy: matrix: go-version: [1.21.x, 1.x] - platform: [ubuntu-latest, macos-latest] + platform: [ubuntu-latest, macos-latest, windows-latest] uses: ./.github/workflows/ci-test-go.yml + secrets: inherit with: go-version: ${{ matrix.go-version }} fail-fast: true platform: ${{ matrix.platform }} project-directory: "." rootless-docker: false - run-tests: ${{ matrix.platform == 'ubuntu-latest' }} + run-tests: true ryuk-disabled: false # The job below is a copy of the job above, but with ryuk disabled. @@ -46,6 +47,7 @@ jobs: matrix: go-version: [1.21.x, 1.x] uses: ./.github/workflows/ci-test-go.yml + secrets: inherit with: go-version: ${{ matrix.go-version }} fail-fast: false @@ -64,6 +66,7 @@ jobs: go-version: [1.21.x, 1.x] platform: [ubuntu-latest] uses: ./.github/workflows/ci-test-go.yml + secrets: inherit with: go-version: ${{ matrix.go-version }} fail-fast: false @@ -79,6 +82,7 @@ jobs: go-version: [1.21.x, 1.x] platform: [ubuntu-latest, macos-latest, windows-latest] uses: ./.github/workflows/ci-test-go.yml + secrets: inherit with: go-version: ${{ matrix.go-version }} fail-fast: true @@ -96,6 +100,7 @@ jobs: platform: [ubuntu-latest] module: [artemis, cassandra, chroma, clickhouse, cockroachdb, compose, consul, couchbase, elasticsearch, gcloud, inbucket, influxdb, k3s, k6, kafka, localstack, mariadb, milvus, minio, mockserver, mongodb, mssql, mysql, nats, neo4j, ollama, openfga, openldap, opensearch, postgres, pulsar, qdrant, rabbitmq, redis, redpanda, registry, surrealdb, vault, weaviate] uses: ./.github/workflows/ci-test-go.yml + secrets: inherit with: go-version: ${{ matrix.go-version }} fail-fast: false @@ -111,6 +116,7 @@ jobs: matrix: module: [nginx, toxiproxy] uses: ./.github/workflows/ci-test-go.yml + secrets: inherit with: go-version: "1.21.x" fail-fast: true diff --git a/config_test.go b/config_test.go index 516b05218e..342f78b303 100644 --- a/config_test.go +++ b/config_test.go @@ -25,21 +25,13 @@ func TestReadConfig(t *testing.T) { t.Run("Config is read just once", func(t *testing.T) { t.Setenv("HOME", "") t.Setenv("USERPROFILE", "") // Windows support - t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") - - cfg := testcontainers.ReadConfig() - - expected := testcontainers.TestcontainersConfig{ - RyukDisabled: true, - Config: config.Config{ - RyukDisabled: true, - }, - } - assert.Equal(t, expected, cfg) + t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") + cfg1 := testcontainers.ReadConfig() t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "false") - cfg = testcontainers.ReadConfig() - assert.Equal(t, expected, cfg) + cfg2 := testcontainers.ReadConfig() + + assert.Equal(t, cfg1, cfg2) }) } diff --git a/docker_auth_test.go b/docker_auth_test.go index 514cf753c7..6508ffee0f 100644 --- a/docker_auth_test.go +++ b/docker_auth_test.go @@ -26,81 +26,96 @@ var indexDockerIO = core.IndexDockerIO func TestGetDockerConfig(t *testing.T) { const expectedErrorMessage = "Expected to find %s in auth configs" + t.Setenv("DOCKER_CONFIG", t.TempDir()) + // Verify that the default docker config file exists before any test in this suite runs. - // Then, we can safely run the tests that rely on it. + // Then, we can safely run the tests that rely on it. If it does not exist, we create it + // using the content of the testdata/.docker/config.json file into a temporary directory. defaultCfg, err := dockercfg.LoadDefaultConfig() - require.NoError(t, err) - require.NotEmpty(t, defaultCfg) + if err != nil { + // create docker config file + bs, err := os.ReadFile(filepath.Join(testDockerConfigDirPath, "config.json")) + require.NoError(t, err) - t.Run("without DOCKER_CONFIG env var retrieves default", func(t *testing.T) { - t.Setenv("DOCKER_CONFIG", "") + defaultCfgPath, err := dockercfg.ConfigPath() + require.NoError(t, err) - cfg, err := getDockerConfig() + // write file to default location + err = os.WriteFile(defaultCfgPath, bs, 0644) require.NoError(t, err) - require.NotEmpty(t, cfg) - assert.Equal(t, defaultCfg, cfg) + defaultCfg, err = dockercfg.LoadDefaultConfig() + require.NoError(t, err) + } + require.NotEmpty(t, defaultCfg) + + t.Run("without DOCKER_CONFIG env var retrieves default", func(tt *testing.T) { + cfg, err := getDockerConfig() + require.NoError(tt, err) + require.NotEmpty(tt, cfg) + + assert.Equal(tt, defaultCfg, cfg) }) - t.Run("with DOCKER_CONFIG env var pointing to a non-existing file raises error", func(t *testing.T) { - t.Setenv("DOCKER_CONFIG", filepath.Join(testDockerConfigDirPath, "non-existing")) + t.Run("with DOCKER_CONFIG env var pointing to a non-existing file raises error", func(tt *testing.T) { + tt.Setenv("DOCKER_CONFIG", filepath.Join(testDockerConfigDirPath, "non-existing")) cfg, err := getDockerConfig() - require.Error(t, err) - require.Empty(t, cfg) + require.Error(tt, err) + require.Empty(tt, cfg) }) - t.Run("with DOCKER_CONFIG env var", func(t *testing.T) { - t.Setenv("DOCKER_CONFIG", testDockerConfigDirPath) + t.Run("with DOCKER_CONFIG env var", func(tt *testing.T) { + tt.Setenv("DOCKER_CONFIG", testDockerConfigDirPath) cfg, err := getDockerConfig() - require.NoError(t, err) - require.NotEmpty(t, cfg) + require.NoError(tt, err) + require.NotEmpty(tt, cfg) - assert.Len(t, cfg.AuthConfigs, 3) + assert.Len(tt, cfg.AuthConfigs, 3) authCfgs := cfg.AuthConfigs if _, ok := authCfgs[indexDockerIO]; !ok { - t.Errorf(expectedErrorMessage, indexDockerIO) + tt.Errorf(expectedErrorMessage, indexDockerIO) } if _, ok := authCfgs["https://example.com"]; !ok { - t.Errorf(expectedErrorMessage, "https://example.com") + tt.Errorf(expectedErrorMessage, "https://example.com") } if _, ok := authCfgs["https://my.private.registry"]; !ok { - t.Errorf(expectedErrorMessage, "https://my.private.registry") + tt.Errorf(expectedErrorMessage, "https://my.private.registry") } }) - t.Run("DOCKER_AUTH_CONFIG env var takes precedence", func(t *testing.T) { - t.Setenv("DOCKER_AUTH_CONFIG", `{ + t.Run("DOCKER_AUTH_CONFIG env var takes precedence", func(tt *testing.T) { + tt.Setenv("DOCKER_AUTH_CONFIG", `{ "auths": { "`+exampleAuth+`": {} }, "credsStore": "desktop" }`) - t.Setenv("DOCKER_CONFIG", testDockerConfigDirPath) + tt.Setenv("DOCKER_CONFIG", testDockerConfigDirPath) cfg, err := getDockerConfig() - require.NoError(t, err) - require.NotEmpty(t, cfg) + require.NoError(tt, err) + require.NotEmpty(tt, cfg) - assert.Len(t, cfg.AuthConfigs, 1) + assert.Len(tt, cfg.AuthConfigs, 1) authCfgs := cfg.AuthConfigs if _, ok := authCfgs[indexDockerIO]; ok { - t.Errorf("Not expected to find %s in auth configs", indexDockerIO) + tt.Errorf("Not expected to find %s in auth configs", indexDockerIO) } if _, ok := authCfgs[exampleAuth]; !ok { - t.Errorf(expectedErrorMessage, exampleAuth) + tt.Errorf(expectedErrorMessage, exampleAuth) } }) - t.Run("retrieve auth with DOCKER_AUTH_CONFIG env var", func(t *testing.T) { + t.Run("retrieve auth with DOCKER_AUTH_CONFIG env var", func(tt *testing.T) { base64 := "Z29waGVyOnNlY3JldA==" // gopher:secret - t.Setenv("DOCKER_AUTH_CONFIG", `{ + tt.Setenv("DOCKER_AUTH_CONFIG", `{ "auths": { "`+exampleAuth+`": { "username": "gopher", "password": "secret", "auth": "`+base64+`" } }, @@ -108,21 +123,21 @@ func TestGetDockerConfig(t *testing.T) { }`) registry, cfg, err := DockerImageAuth(context.Background(), exampleAuth+"/my/image:latest") - require.NoError(t, err) - require.NotEmpty(t, cfg) + require.NoError(tt, err) + require.NotEmpty(tt, cfg) - assert.Equal(t, exampleAuth, registry) - assert.Equal(t, "gopher", cfg.Username) - assert.Equal(t, "secret", cfg.Password) - assert.Equal(t, base64, cfg.Auth) + assert.Equal(tt, exampleAuth, registry) + assert.Equal(tt, "gopher", cfg.Username) + assert.Equal(tt, "secret", cfg.Password) + assert.Equal(tt, base64, cfg.Auth) }) - t.Run("match registry authentication by host", func(t *testing.T) { + t.Run("match registry authentication by host", func(tt *testing.T) { base64 := "Z29waGVyOnNlY3JldA==" // gopher:secret imageReg := "example-auth.com" imagePath := "/my/image:latest" - t.Setenv("DOCKER_AUTH_CONFIG", `{ + tt.Setenv("DOCKER_AUTH_CONFIG", `{ "auths": { "`+exampleAuth+`": { "username": "gopher", "password": "secret", "auth": "`+base64+`" } }, @@ -130,22 +145,22 @@ func TestGetDockerConfig(t *testing.T) { }`) registry, cfg, err := DockerImageAuth(context.Background(), imageReg+imagePath) - require.NoError(t, err) - require.NotEmpty(t, cfg) + require.NoError(tt, err) + require.NotEmpty(tt, cfg) - assert.Equal(t, imageReg, registry) - assert.Equal(t, "gopher", cfg.Username) - assert.Equal(t, "secret", cfg.Password) - assert.Equal(t, base64, cfg.Auth) + assert.Equal(tt, imageReg, registry) + assert.Equal(tt, "gopher", cfg.Username) + assert.Equal(tt, "secret", cfg.Password) + assert.Equal(tt, base64, cfg.Auth) }) - t.Run("fail to match registry authentication due to invalid host", func(t *testing.T) { + t.Run("fail to match registry authentication due to invalid host", func(tt *testing.T) { base64 := "Z29waGVyOnNlY3JldA==" // gopher:secret imageReg := "example-auth.com" imagePath := "/my/image:latest" invalidRegistryURL := "://invalid-host" - t.Setenv("DOCKER_AUTH_CONFIG", `{ + tt.Setenv("DOCKER_AUTH_CONFIG", `{ "auths": { "`+invalidRegistryURL+`": { "username": "gopher", "password": "secret", "auth": "`+base64+`" } }, @@ -153,10 +168,10 @@ func TestGetDockerConfig(t *testing.T) { }`) registry, cfg, err := DockerImageAuth(context.Background(), imageReg+imagePath) - require.Equal(t, err, dockercfg.ErrCredentialsNotFound) - require.Empty(t, cfg) + require.Equal(tt, err, dockercfg.ErrCredentialsNotFound) + require.Empty(tt, cfg) - assert.Equal(t, imageReg, registry) + assert.Equal(tt, imageReg, registry) }) } diff --git a/docker_client_test.go b/docker_client_test.go index 4b582493bb..4908f24741 100644 --- a/docker_client_test.go +++ b/docker_client_test.go @@ -13,6 +13,10 @@ func TestGetDockerInfo(t *testing.T) { ctx := context.Background() c, err := NewDockerClientWithOpts(ctx) require.NoError(t, err) + t.Cleanup(func() { + err := c.Close() + require.NoError(t, err) + }) info, err := c.Info(ctx) require.NoError(t, err) @@ -23,6 +27,10 @@ func TestGetDockerInfo(t *testing.T) { ctx := context.Background() c, err := NewDockerClientWithOpts(ctx) require.NoError(t, err) + t.Cleanup(func() { + err := c.Close() + require.NoError(t, err) + }) count := 1024 wg := sync.WaitGroup{} diff --git a/docker_files_test.go b/docker_files_test.go index 6fcfc92a0b..a40440f882 100644 --- a/docker_files_test.go +++ b/docker_files_test.go @@ -30,7 +30,7 @@ func TestCopyFileToContainer(t *testing.T) { container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "docker.io/bash", + Image: "docker.io/bash:5.2.26", Files: []testcontainers.ContainerFile{ { Reader: r, @@ -51,8 +51,7 @@ func TestCopyFileToContainer(t *testing.T) { } func TestCopyFileToRunningContainer(t *testing.T) { - ctx, cnl := context.WithTimeout(context.Background(), 30*time.Second) - defer cnl() + ctx := context.Background() // Not using the assertations here to avoid leaking the library into the example // copyFileAfterCreate { @@ -108,7 +107,7 @@ func TestCopyDirectoryToContainer(t *testing.T) { container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "docker.io/bash", + Image: "docker.io/bash:5.2.26", Files: []testcontainers.ContainerFile{ { HostFilePath: dataDirectory, @@ -131,8 +130,7 @@ func TestCopyDirectoryToContainer(t *testing.T) { } func TestCopyDirectoryToRunningContainerAsFile(t *testing.T) { - ctx, cnl := context.WithTimeout(context.Background(), 30*time.Second) - defer cnl() + ctx := context.Background() // copyDirectoryToRunningContainerAsFile { dataDirectory, err := filepath.Abs(filepath.Join(".", "testdata")) @@ -146,7 +144,7 @@ func TestCopyDirectoryToRunningContainerAsFile(t *testing.T) { container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "docker.io/bash", + Image: "docker.io/bash:5.2.26", Files: []testcontainers.ContainerFile{ { HostFilePath: waitForPath, @@ -155,6 +153,20 @@ func TestCopyDirectoryToRunningContainerAsFile(t *testing.T) { }, }, Cmd: []string{"bash", "/waitForHello.sh"}, + LifecycleHooks: []testcontainers.ContainerLifecycleHooks{ + { + PostReadies: []testcontainers.ContainerHook{ + func(ctx context.Context, container testcontainers.Container) error { + // as the container is started, we can create the directory right after the container is ready + _, _, err = container.Exec(ctx, []string{"mkdir", "-p", "/scripts"}) + if err != nil { + return err + } + return nil + }, + }, + }, + }, }, Started: true, }) @@ -162,12 +174,6 @@ func TestCopyDirectoryToRunningContainerAsFile(t *testing.T) { t.Fatal(err) } - // as the container is started, we can create the directory first - _, _, err = container.Exec(ctx, []string{"mkdir", "-p", "/scripts"}) - if err != nil { - t.Fatal(err) - } - // because the container path is a directory, it will use the copy dir method as fallback err = container.CopyFileToContainer(ctx, dataDirectory, "/scripts", 0o700) if err != nil { @@ -180,8 +186,7 @@ func TestCopyDirectoryToRunningContainerAsFile(t *testing.T) { } func TestCopyDirectoryToRunningContainerAsDir(t *testing.T) { - ctx, cnl := context.WithTimeout(context.Background(), 30*time.Second) - defer cnl() + ctx := context.Background() // Not using the assertations here to avoid leaking the library into the example // copyDirectoryToRunningContainerAsDir { @@ -196,7 +201,7 @@ func TestCopyDirectoryToRunningContainerAsDir(t *testing.T) { container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ - Image: "docker.io/bash", + Image: "docker.io/bash:5.2.26", Files: []testcontainers.ContainerFile{ { HostFilePath: waitForPath, @@ -205,6 +210,20 @@ func TestCopyDirectoryToRunningContainerAsDir(t *testing.T) { }, }, Cmd: []string{"bash", "/waitForHello.sh"}, + LifecycleHooks: []testcontainers.ContainerLifecycleHooks{ + { + PostReadies: []testcontainers.ContainerHook{ + func(ctx context.Context, container testcontainers.Container) error { + // as the container is started, we can create the directory right after the container is ready + _, _, err = container.Exec(ctx, []string{"mkdir", "-p", "/scripts"}) + if err != nil { + return err + } + return nil + }, + }, + }, + }, }, Started: true, }) @@ -212,12 +231,6 @@ func TestCopyDirectoryToRunningContainerAsDir(t *testing.T) { t.Fatal(err) } - // as the container is started, we can create the directory first - _, _, err = container.Exec(ctx, []string{"mkdir", "-p", "/scripts"}) - if err != nil { - t.Fatal(err) - } - err = container.CopyDirToContainer(ctx, dataDirectory, "/scripts", 0o700) if err != nil { t.Fatal(err) diff --git a/docker_test.go b/docker_test.go index 45e276b1ed..a925742ada 100644 --- a/docker_test.go +++ b/docker_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go/internal/core" "github.com/testcontainers/testcontainers-go/wait" ) @@ -45,6 +46,10 @@ func init() { } func TestContainerWithHostNetworkOptions(t *testing.T) { + if core.IsWindows() { + t.Skip("Skipping test for host network access when running on Windows") + } + if os.Getenv("XDG_RUNTIME_DIR") != "" { t.Skip("Skipping test that requires host network access when running in a container") } @@ -100,6 +105,10 @@ func TestContainerWithHostNetworkOptions(t *testing.T) { } func TestContainerWithHostNetworkOptions_UseExposePortsFromImageConfigs(t *testing.T) { + if core.IsWindows() { + t.Skip("Skipping test for host network access when running on Windows") + } + ctx := context.Background() gcr := GenericContainerRequest{ ContainerRequest: ContainerRequest{ @@ -164,6 +173,10 @@ func TestContainerWithNetworkModeAndNetworkTogether(t *testing.T) { } func TestContainerWithHostNetwork(t *testing.T) { + if core.IsWindows() { + t.Skip("Skipping test for host network access when running on Windows") + } + if os.Getenv("XDG_RUNTIME_DIR") != "" { t.Skip("Skipping test that requires host network access when running in a container") } diff --git a/generic_test.go b/generic_test.go index 72688876ec..1cd355b66c 100644 --- a/generic_test.go +++ b/generic_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go/internal/core" "github.com/testcontainers/testcontainers-go/wait" ) @@ -144,7 +145,9 @@ func TestGenericReusableContainerInSubprocess(t *testing.T) { func createReuseContainerInSubprocess(t *testing.T) string { cmd := exec.Command(os.Args[0], "-test.run=TestHelperContainerStarterProcess") - cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} + cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1", "TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=" + core.ExtractDockerSocket(context.Background()), "DOCKER_HOST=" + core.ExtractDockerHost(context.Background())} + + t.Log("Calling subprocess with env:", cmd.Env) output, err := cmd.CombinedOutput() require.NoError(t, err, string(output)) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 6c9cb0dd3c..65909e7b85 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -34,21 +34,14 @@ func TestReadConfig(t *testing.T) { t.Setenv("HOME", "") t.Setenv("USERPROFILE", "") // Windows support t.Setenv("DOCKER_HOST", "") - t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") - - config := Read() - - expected := Config{ - RyukDisabled: true, - Host: "", // docker socket is empty at the properties file - } - assert.Equal(t, expected, config) + t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") + config1 := Read() t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "false") + config2 := Read() - config = Read() - assert.Equal(t, expected, config) + assert.Equal(t, config1, config2) }) } diff --git a/internal/core/docker_host.go b/internal/core/docker_host.go index 0b55ec01c8..b873d4d6de 100644 --- a/internal/core/docker_host.go +++ b/internal/core/docker_host.go @@ -120,7 +120,7 @@ func extractDockerHost(ctx context.Context) string { } // We are not supporting Windows containers at the moment - return DockerSocketPathWithSchema + return DockerSocketSchema + DockerSocketPath } // extractDockerHost Extracts the docker socket from the different alternatives, without caching the result. @@ -141,47 +141,23 @@ func extractDockerSocket(ctx context.Context) string { // and receiving an instance of the Docker API client interface. // This internal method is handy for testing purposes, passing a mock type simulating the desired behaviour. func extractDockerSocketFromClient(ctx context.Context, cli client.APIClient) string { - // check that the socket is not a tcp or unix socket - checkDockerSocketFn := func(socket string) string { - // this use case will cover the case when the docker host is a tcp socket - if strings.HasPrefix(socket, TCPSchema) { - return DockerSocketPath - } - - if strings.HasPrefix(socket, DockerSocketSchema) { - return strings.Replace(socket, DockerSocketSchema, "", 1) - } - - return socket - } - tcHost, err := testcontainersHostFromProperties(ctx) if err == nil { - return checkDockerSocketFn(tcHost) + return checkDefaultDockerSocket(ctx, cli, tcHost) } testcontainersDockerSocket, err := dockerSocketOverridePath(ctx) if err == nil { - return checkDockerSocketFn(testcontainersDockerSocket) - } - - info, err := cli.Info(ctx) - if err != nil { - panic(err) // Docker Info is required to get the Operating System - } - - // Because Docker Desktop runs in a VM, we need to use the default docker path for rootless docker - if info.OperatingSystem == "Docker Desktop" { - if IsWindows() { - return WindowsDockerSocketPath + if strings.HasPrefix(testcontainersDockerSocket, TCPSchema) { + return checkDefaultDockerSocket(ctx, cli, testcontainersDockerSocket) } - return DockerSocketPath + return strings.Replace(testcontainersDockerSocket, DockerSocketSchema, "", 1) } dockerHost := extractDockerHost(ctx) - return checkDockerSocketFn(dockerHost) + return checkDefaultDockerSocket(ctx, cli, dockerHost) } // dockerHostFromEnv returns the docker host from the DOCKER_HOST environment variable, if it's not empty @@ -232,7 +208,7 @@ func dockerSocketOverridePath(ctx context.Context) (string, error) { // and the socket exists func dockerSocketPath(ctx context.Context) (string, error) { if fileExists(DockerSocketPath) { - return DockerSocketPathWithSchema, nil + return DockerSocketSchema + DockerSocketPath, nil } return "", ErrSocketNotFoundInPath diff --git a/internal/core/docker_host_test.go b/internal/core/docker_host_test.go index dbdbaa31fe..7cd3761518 100644 --- a/internal/core/docker_host_test.go +++ b/internal/core/docker_host_test.go @@ -17,10 +17,7 @@ import ( // testRemoteHost is a testcontainers host defined in the properties file for testing purposes var testRemoteHost = TCPSchema + "127.0.0.1:12345" -var ( - originalDockerSocketPath string - originalDockerSocketPathWithSchema string -) +var originalDockerSocketPath string var ( originalDockerSocketOverride string @@ -29,7 +26,6 @@ var ( func init() { originalDockerSocketPath = DockerSocketPath - originalDockerSocketPathWithSchema = DockerSocketPathWithSchema originalDockerSocketOverride = os.Getenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE") @@ -48,15 +44,11 @@ func TestExtractDockerHost(t *testing.T) { t.Setenv("USERPROFILE", tmpDir) // Windows support t.Run("Docker Host as extracted just once", func(t *testing.T) { - expected := "/path/to/docker.sock" - t.Setenv("DOCKER_HOST", expected) - host := ExtractDockerHost(context.Background()) - - assert.Equal(t, expected, host) + expected := ExtractDockerHost(context.Background()) t.Setenv("DOCKER_HOST", "/path/to/another/docker.sock") - host = ExtractDockerHost(context.Background()) + host := ExtractDockerHost(context.Background()) assert.Equal(t, expected, host) }) @@ -86,7 +78,7 @@ func TestExtractDockerHost(t *testing.T) { host := extractDockerHost(context.WithValue(ctx, DockerHostContextKey, "path-to-docker-sock")) - assert.Equal(t, DockerSocketPathWithSchema, host) + assert.Equal(t, DockerSocketSchema+DockerSocketPath, host) }) t.Run("Malformed Schema Docker Host is passed in context", func(t *testing.T) { @@ -96,7 +88,7 @@ func TestExtractDockerHost(t *testing.T) { host := extractDockerHost(context.WithValue(ctx, DockerHostContextKey, "http://path to docker sock")) - assert.Equal(t, DockerSocketPathWithSchema, host) + assert.Equal(t, DockerSocketSchema+DockerSocketPath, host) }) t.Run("Unix Docker Host is passed in context", func(t *testing.T) { @@ -133,7 +125,7 @@ func TestExtractDockerHost(t *testing.T) { setupRootlessNotFound(t) host := extractDockerHost(context.Background()) - assert.Equal(t, DockerSocketPathWithSchema, host) + assert.Equal(t, DockerSocketSchema+DockerSocketPath, host) }) t.Run("Extract Docker socket", func(t *testing.T) { @@ -268,10 +260,12 @@ func TestExtractDockerHost(t *testing.T) { } // mockCli is a mock implementation of client.APIClient, which is handy for simulating -// different operating systems. +// different operating systems and local VS remote Docker hosts. type mockCli struct { client.APIClient - OS string + OS string // used to detect if the Docker Desktop is running + OSType string // used to detect if the Docker client is running in a remote Docker host: linux should represent a remote Docker host + infoErr error // used to simulate an error when calling Info } // Info returns a mock implementation of types.Info, which is handy for detecting the operating system, @@ -279,7 +273,8 @@ type mockCli struct { func (m mockCli) Info(ctx context.Context) (system.Info, error) { return system.Info{ OperatingSystem: m.OS, - }, nil + OSType: m.OSType, + }, m.infoErr } func TestExtractDockerSocketFromClient(t *testing.T) { @@ -350,6 +345,25 @@ func TestExtractDockerSocketFromClient(t *testing.T) { assert.Equal(t, DockerSocketPath, socket) }) + t.Run("TCP Docker Socket is passed as Testcontainers properties (Remote Linux host in non-Windows)", func(t *testing.T) { + if IsWindows() { + t.Skip("Skip for Windows") + } + + content := "tc.host=" + testRemoteHost + setupTestcontainersProperties(t, content) + + t.Setenv("GOOS", "linux") + + t.Cleanup(resetSocketOverrideFn) + + ctx := context.Background() + + socket := extractDockerSocketFromClient(ctx, mockCli{OSType: "linux"}) + + assert.Equal(t, DockerSocketPath, socket) + }) + t.Run("Unix Docker Socket is passed as DOCKER_HOST variable (Docker Desktop for Windows)", func(t *testing.T) { t.Setenv("GOOS", "windows") setupTestcontainersProperties(t, "") @@ -362,7 +376,22 @@ func TestExtractDockerSocketFromClient(t *testing.T) { socket := extractDockerSocketFromClient(ctx, mockCli{OS: "Docker Desktop"}) - assert.Equal(t, WindowsDockerSocketPath, socket) + assert.Equal(t, windowsDockerSocketPath, socket) + }) + + t.Run("TCP Docker Socket is passed as Testcontainers properties (Remote Linux host in Windows)", func(t *testing.T) { + content := "tc.host=" + testRemoteHost + setupTestcontainersProperties(t, content) + + t.Setenv("GOOS", "windows") + + t.Cleanup(resetSocketOverrideFn) + + ctx := context.Background() + + socket := extractDockerSocketFromClient(ctx, mockCli{OSType: "linux"}) + + assert.Equal(t, windowsDockerSocketPath, socket) }) t.Run("Unix Docker Socket is passed as DOCKER_HOST variable (Not Docker Desktop)", func(t *testing.T) { @@ -411,6 +440,22 @@ func TestExtractDockerSocketFromClient(t *testing.T) { assert.Equal(t, "/this/is/a/sample.sock", socket) }) + + t.Run("Unix Docker Socket for rootless", func(tt *testing.T) { + if IsWindows() { + tt.Skip("rootless Docker is not supported on Windows") + } + + tmpDir := tt.TempDir() + + tt.Setenv("XDG_RUNTIME_DIR", tmpDir) + err := createTmpDockerSocket(tmpDir) + require.NoError(tt, err) + + socket := extractDockerSocketFromClient(context.Background(), mockCli{OS: "Docker Desktop"}) + + assert.Equal(tt, filepath.Join(tmpDir, "docker.sock"), socket) + }) } func TestInAContainer(t *testing.T) { @@ -466,7 +511,6 @@ func setupDockerHostNotFound(t *testing.T) { func setupDockerSocket(t *testing.T) string { t.Cleanup(func() { DockerSocketPath = originalDockerSocketPath - DockerSocketPathWithSchema = originalDockerSocketPathWithSchema }) tmpDir := t.TempDir() @@ -475,7 +519,6 @@ func setupDockerSocket(t *testing.T) string { require.NoError(t, err) DockerSocketPath = tmpSocket - DockerSocketPathWithSchema = tmpSchema + tmpSocket return tmpSchema + tmpSocket } @@ -483,7 +526,6 @@ func setupDockerSocket(t *testing.T) string { func setupDockerSocketNotFound(t *testing.T) { t.Cleanup(func() { DockerSocketPath = originalDockerSocketPath - DockerSocketPathWithSchema = originalDockerSocketPathWithSchema }) tmpDir := t.TempDir() diff --git a/internal/core/docker_socket.go b/internal/core/docker_socket.go index b0c0c8481b..56279f3202 100644 --- a/internal/core/docker_socket.go +++ b/internal/core/docker_socket.go @@ -1,6 +1,7 @@ package core import ( + "context" "net/url" "strings" @@ -13,14 +14,11 @@ var DockerSocketSchema = "unix://" // DockerSocketPath is the path to the docker socket under unix systems. var DockerSocketPath = "/var/run/docker.sock" -// DockerSocketPathWithSchema is the path to the docker socket under unix systems with the unix schema. -var DockerSocketPathWithSchema = DockerSocketSchema + DockerSocketPath - // TCPSchema is the tcp schema. -var TCPSchema = "tcp://" +const TCPSchema = "tcp://" -// WindowsDockerSocketPath is the path to the docker socket under windows systems. -var WindowsDockerSocketPath = "//var/run/docker.sock" +// windowsDockerSocketPath is the path to the docker socket under windows systems and Linux containers. +const windowsDockerSocketPath = "//var/run/docker.sock" func init() { const DefaultDockerHost = client.DefaultDockerHost @@ -44,6 +42,83 @@ func init() { // save future pain from innocent users. DockerSocketPath = "/" + DockerSocketPath } - DockerSocketPathWithSchema = DockerSocketSchema + DockerSocketPath } } + +// checkDefaultDockerSocket checks the docker socket path and returns the correct path depending on the Docker client configuration, +// the operating system, and the Docker info. +// It will use the Docker client infrastructure to get the correct path: +// - If the Docker client is running in a local Docker host, it will return "/var/run/docker.sock" on Unix, or "//./pipe/docker_engine" on Windows. +// - If the Docker client is running in Docker Desktop, it will return "/var/run/docker.sock" on Unix, or "//./pipe/docker_engine" on Windows. +// - If the Docker client is running in a remote Docker host, it will return "/var/run/docker.sock" on Unix, or "//var/run/docker.sock" on Windows. +// - If the Docker client is running in a rootless Docker, it will return the proper path depending on the rootless Docker configuration. Not available for windows. +// If the Docker info cannot be retrieved, the program will panic. +// This internal method is handy for testing purposes, passing a mock type simulating the desired behaviour. +func checkDefaultDockerSocket(ctx context.Context, cli client.APIClient, socket string) string { + info, err := cli.Info(ctx) + if err != nil { + panic(err) // Docker Info is required to get the client socket + } + + // this default path will come from the default docker client, which for + // unix systems is "/var/run/docker.sock", and for Windows is "//./pipe/docker_engine" + defaultDockerSocketPath := "/var/run/docker.sock" + defaultSocketSchema := "unix://" + defaultRemoteDockerSocketPath := defaultDockerSocketPath + defaultRootlessDockerSocketPath := defaultDockerSocketPath + + if IsWindows() { + // the path to the docker socket under windows systems and Windows containers. + defaultDockerSocketPath = "//./pipe/docker_engine" + defaultSocketSchema = "npipe://" + defaultRemoteDockerSocketPath = "//var/run/docker.sock" + defaultRootlessDockerSocketPath = "//var/run/docker.sock" + } else { + // rootless docker socket path is not supported on Windows + defaultRootlessDockerSocketPath, err = rootlessDockerSocketPath(ctx) + if err != nil { + defaultRootlessDockerSocketPath = defaultDockerSocketPath + } + + // rootless is enabled, so we need to use the default docker paths for rootless docker. + if defaultDockerSocketPath != defaultRootlessDockerSocketPath { + defaultDockerSocketPath = defaultRootlessDockerSocketPath + defaultRemoteDockerSocketPath = defaultRootlessDockerSocketPath + } + } + + if info.OperatingSystem == "Docker Desktop" { + // Because Docker Desktop runs in a VM, we need to use the default docker path for rootless docker. + // For Windows it will be "//var/run/docker.sock" and for Unix it will be "/var/run/docker.sock" + if strings.HasPrefix(defaultRootlessDockerSocketPath, defaultSocketSchema) { + return strings.Replace(defaultRootlessDockerSocketPath, defaultSocketSchema, "", 1) + } + + return defaultRootlessDockerSocketPath + } + + if info.OSType == "linux" { + // we are using a remote Docker host, so we need to use the default docker path for rootless docker. + // For Windows it will be "//var/run/docker.sock" and for Unix it will be "/var/run/docker.sock" + if strings.HasPrefix(defaultRemoteDockerSocketPath, defaultSocketSchema) { + return strings.Replace(defaultRemoteDockerSocketPath, defaultSocketSchema, "", 1) + } + + return defaultRemoteDockerSocketPath + } + + // check that the socket is not a tcp or unix socket, + // including potential remote Docker hosts and rootless Docker. + + // this use case will cover the case when the docker host is a tcp socket + if strings.HasPrefix(socket, TCPSchema) { + return defaultDockerSocketPath + } + + // this use case will cover the case when the docker host is a unix or npipe socket + if strings.HasPrefix(socket, defaultSocketSchema) { + return strings.Replace(socket, defaultSocketSchema, "", 1) + } + + return socket +} diff --git a/internal/core/docker_socket_test.go b/internal/core/docker_socket_test.go new file mode 100644 index 0000000000..9a544bb805 --- /dev/null +++ b/internal/core/docker_socket_test.go @@ -0,0 +1,239 @@ +package core + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCheckDefaultDockerSocket(t *testing.T) { + t.Run("Docker client panics", func(tt *testing.T) { + defer func() { + if r := recover(); r == nil { + tt.Errorf("expected panic") + } + }() + + checkDefaultDockerSocket(context.Background(), mockCli{infoErr: errors.New("info should panic")}, "") + }) + + t.Run("Local Docker on Unix", func(tt *testing.T) { + if IsWindows() { + tt.Skip("skipping test on Windows") + } + + socket := "unix:///var/run/docker.sock" + expected := "/var/run/docker.sock" + + s := checkDefaultDockerSocket(context.Background(), mockCli{}, socket) + if s != expected { + tt.Errorf("expected %s, got %s", expected, s) + } + }) + + t.Run("Rootless Docker on Unix: home dir", func(tt *testing.T) { + if IsWindows() { + tt.Skip("skipping test on Windows") + } + + tmpDir := tt.TempDir() + + tt.Setenv("HOME", tmpDir) + runDir := filepath.Join(tmpDir, ".docker", "run") + err := createTmpDockerSocket(runDir) + require.NoError(t, err) + + socket := "unix://" + tmpDir + "/.docker/run/docker.sock" + expected := tmpDir + "/.docker/run/docker.sock" + + s := checkDefaultDockerSocket(context.Background(), mockCli{OS: "Docker Desktop"}, socket) + if s != expected { + tt.Errorf("expected %s, got %s", expected, s) + } + }) + + t.Run("Rootless Docker on Unix: XDG_RUNTIME_DIR", func(tt *testing.T) { + if IsWindows() { + tt.Skip("skipping test on Windows") + } + + tmpDir := tt.TempDir() + tt.Setenv("XDG_RUNTIME_DIR", tmpDir) + err := createTmpDockerSocket(tmpDir) + require.NoError(tt, err) + + socket := "unix://" + tmpDir + "/docker.sock" + expected := tmpDir + "/docker.sock" + + s := checkDefaultDockerSocket(context.Background(), mockCli{OS: "Docker Desktop"}, socket) + if s != expected { + tt.Errorf("expected %s, got %s", expected, s) + } + }) + + t.Run("Rootless Docker on Unix: home desktop dir", func(tt *testing.T) { + if IsWindows() { + tt.Skip("skipping test on Windows") + } + + tmpDir := tt.TempDir() + + tt.Setenv("HOME", tmpDir) + desktopDir := filepath.Join(tmpDir, ".docker", "desktop") + err := createTmpDockerSocket(desktopDir) + require.NoError(tt, err) + + socket := "unix://" + tmpDir + "/.docker/desktop/docker.sock" + expected := tmpDir + "/.docker/desktop/docker.sock" + + s := checkDefaultDockerSocket(context.Background(), mockCli{OS: "Docker Desktop"}, socket) + if s != expected { + tt.Errorf("expected %s, got %s", expected, s) + } + }) + + t.Run("Rootless Docker on Unix: run dir", func(tt *testing.T) { + if IsWindows() { + tt.Skip("skipping test on Windows") + } + + tmpDir := tt.TempDir() + + homeDir := filepath.Join(tmpDir, "home") + err := createTmpDir(homeDir) + require.NoError(tt, err) + tt.Setenv("HOME", homeDir) + + baseRunDir = tmpDir + tt.Cleanup(func() { + baseRunDir = originalBaseRunDir + os.Setenv("HOME", originalHomeDir) + os.Setenv("USERPROFILE", originalHomeDir) + os.Setenv("XDG_RUNTIME_DIR", originalXDGRuntimeDir) + }) + + uid := os.Getuid() + runDir := filepath.Join(tmpDir, "user", fmt.Sprintf("%d", uid)) + err = createTmpDockerSocket(runDir) + require.NoError(tt, err) + + socket := "unix://" + runDir + "/docker.sock" + expected := runDir + "/docker.sock" + + s := checkDefaultDockerSocket(context.Background(), mockCli{OS: "Docker Desktop"}, socket) + if s != expected { + tt.Errorf("expected %s, got %s", expected, s) + } + }) + + t.Run("Local Docker on Windows", func(tt *testing.T) { + if !IsWindows() { + tt.Skip("skipping test on non-Windows") + } + + tt.Setenv("GOOS", "windows") + + socket := "npipe:////./pipe/docker_engine" + expected := "//./pipe/docker_engine" + + s := checkDefaultDockerSocket(context.Background(), mockCli{}, socket) + if s != expected { + tt.Errorf("expected %s, got %s", expected, s) + } + }) + + t.Run("Docker Desktop on Unix", func(tt *testing.T) { + if IsWindows() { + tt.Skip("skipping test on Windows") + } + + socket := "unix:///var/run/docker.sock" + expected := "/var/run/docker.sock" + + s := checkDefaultDockerSocket(context.Background(), mockCli{OS: "Docker Desktop"}, socket) + if s != expected { + tt.Errorf("expected %s, got %s", expected, s) + } + }) + + t.Run("Docker Desktop on Windows", func(tt *testing.T) { + if !IsWindows() { + tt.Skip("skipping test on non-Windows") + } + + tt.Setenv("GOOS", "windows") + + socket := "npipe:////./pipe/docker_engine" + expected := "//var/run/docker.sock" + + s := checkDefaultDockerSocket(context.Background(), mockCli{OS: "Docker Desktop"}, socket) + if s != expected { + tt.Errorf("expected %s, got %s", expected, s) + } + }) + + t.Run("Remote Unix Docker on Unix", func(tt *testing.T) { + if IsWindows() { + tt.Skip("skipping test on Windows") + } + + socket := "tcp://127.0.0.1:12345" + expected := "/var/run/docker.sock" + + s := checkDefaultDockerSocket(context.Background(), mockCli{OSType: "linux"}, socket) + if s != expected { + tt.Errorf("expected %s, got %s", expected, s) + } + }) + + t.Run("Remote Unix Docker on Windows", func(tt *testing.T) { + if !IsWindows() { + tt.Skip("skipping test on non-Windows") + } + + tt.Setenv("GOOS", "windows") + + socket := "tcp://127.0.0.1:12345" + expected := "//var/run/docker.sock" + + s := checkDefaultDockerSocket(context.Background(), mockCli{OSType: "linux"}, socket) + if s != expected { + tt.Errorf("expected %s, got %s", expected, s) + } + }) + + t.Run("Remote Windows Docker on Unix", func(tt *testing.T) { + if IsWindows() { + tt.Skip("skipping test on Windows") + } + + socket := "tcp://127.0.0.1:12345" + expected := "/var/run/docker.sock" + + s := checkDefaultDockerSocket(context.Background(), mockCli{OSType: "windows"}, socket) + if s != expected { + tt.Errorf("expected %s, got %s", expected, s) + } + }) + + t.Run("Remote Windows Docker on Windows", func(tt *testing.T) { + if !IsWindows() { + tt.Skip("skipping test on non-Windows") + } + + tt.Setenv("GOOS", "windows") + + socket := "tcp://127.0.0.1:12345" + expected := "//./pipe/docker_engine" + + s := checkDefaultDockerSocket(context.Background(), mockCli{OSType: "windows"}, socket) + if s != expected { + tt.Errorf("expected %s, got %s", expected, s) + } + }) +} diff --git a/logconsumer_test.go b/logconsumer_test.go index 9c9b25fa09..8e54a432c5 100644 --- a/logconsumer_test.go +++ b/logconsumer_test.go @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go/internal/config" + "github.com/testcontainers/testcontainers-go/internal/core" "github.com/testcontainers/testcontainers-go/wait" ) @@ -224,13 +225,17 @@ func TestContainerLogWithErrClosed(t *testing.T) { t.Skip("Skipping as flaky on GitHub Actions, Please see https://github.com/testcontainers/testcontainers-go/issues/1924") } - t.Cleanup(func() { - config.Reset() - }) + if core.IsWindows() { + t.Skip("Skipping as flaky on Windows, Please see https://github.com/testcontainers/testcontainers-go/issues/1924") + } if providerType == ProviderPodman { t.Skip("Docker-in-Docker does not work with rootless Podman") } + + t.Cleanup(func() { + config.Reset() + }) // First spin up a docker-in-docker container, then spin up an inner container within that dind container // Logs are being read from the inner container via the dind container's tcp port, which can be briefly // closed to test behaviour in connection-closed situations. diff --git a/modulegen/_template/ci.yml.tmpl b/modulegen/_template/ci.yml.tmpl index 616dd2bee6..baa200bfc9 100644 --- a/modulegen/_template/ci.yml.tmpl +++ b/modulegen/_template/ci.yml.tmpl @@ -27,15 +27,16 @@ jobs: strategy: matrix: go-version: [1.21.x, 1.x] - platform: [ubuntu-latest, macos-latest] + platform: [ubuntu-latest, macos-latest, windows-latest] uses: ./.github/workflows/ci-test-go.yml + secrets: inherit with: go-version: {{ "${{ matrix.go-version }}" }} fail-fast: true platform: {{ "${{ matrix.platform }}" }} project-directory: "." rootless-docker: false - run-tests: {{ "${{ matrix.platform == 'ubuntu-latest' }}" }} + run-tests: true ryuk-disabled: false # The job below is a copy of the job above, but with ryuk disabled. @@ -46,6 +47,7 @@ jobs: matrix: go-version: [1.21.x, 1.x] uses: ./.github/workflows/ci-test-go.yml + secrets: inherit with: go-version: {{ "${{ matrix.go-version }}" }} fail-fast: false @@ -64,6 +66,7 @@ jobs: go-version: [1.21.x, 1.x] platform: [ubuntu-latest] uses: ./.github/workflows/ci-test-go.yml + secrets: inherit with: go-version: {{ "${{ matrix.go-version }}" }} fail-fast: false @@ -79,6 +82,7 @@ jobs: go-version: [1.21.x, 1.x] platform: [ubuntu-latest, macos-latest, windows-latest] uses: ./.github/workflows/ci-test-go.yml + secrets: inherit with: go-version: {{ "${{ matrix.go-version }}" }} fail-fast: true @@ -96,6 +100,7 @@ jobs: platform: [ubuntu-latest] module: [{{ .Modules }}] uses: ./.github/workflows/ci-test-go.yml + secrets: inherit with: go-version: {{ "${{ matrix.go-version }}" }} fail-fast: false @@ -111,6 +116,7 @@ jobs: matrix: module: [{{ .Examples }}] uses: ./.github/workflows/ci-test-go.yml + secrets: inherit with: go-version: "1.21.x" fail-fast: true diff --git a/modulegen/main_test.go b/modulegen/main_test.go index f108ddb0dd..2542d400a8 100644 --- a/modulegen/main_test.go +++ b/modulegen/main_test.go @@ -441,11 +441,11 @@ func assertModuleGithubWorkflowContent(t *testing.T, moduleWorkflowFile string) modulesList, err := ctx.GetModules() require.NoError(t, err) - assert.Equal(t, " module: ["+strings.Join(modulesList, ", ")+"]", data[96]) + assert.Equal(t, " module: ["+strings.Join(modulesList, ", ")+"]", data[100]) examplesList, err := ctx.GetExamples() require.NoError(t, err) - assert.Equal(t, " module: ["+strings.Join(examplesList, ", ")+"]", data[111]) + assert.Equal(t, " module: ["+strings.Join(examplesList, ", ")+"]", data[116]) } // assert content go.mod diff --git a/network/network.go b/network/network.go index 646ed463a8..528b6cc648 100644 --- a/network/network.go +++ b/network/network.go @@ -14,12 +14,12 @@ import ( // Those existing APIs are deprecated and will be removed in the future, so this function will // implement the new network APIs when they will be available. // By default, the network is created with the following options: -// - Driver: bridge +// - Driver: bridge (nat for Windows hosts) // - Labels: the Testcontainers for Go generic labels, to be managed by Ryuk. Please see the GenericLabels() function // And those options can be modified by the user, using the CreateModifier function field. func New(ctx context.Context, opts ...NetworkCustomizer) (*testcontainers.DockerNetwork, error) { nc := types.NetworkCreate{ - Driver: "bridge", + // we are intentionally not setting the driver to "bridge" here, letting Docker choose the default one. Labels: testcontainers.GenericLabels(), } diff --git a/provider_test.go b/provider_test.go index 2448d84c07..668465aabf 100644 --- a/provider_test.go +++ b/provider_test.go @@ -58,8 +58,10 @@ func TestProviderTypeGetProviderAutodetect(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.tr == ProviderPodman && core.IsWindows() { - t.Skip("Podman provider is not implemented for Windows") + if core.IsWindows() { + if tt.tr == ProviderPodman || tt.DockerHost == podmanSocket { + t.Skip("Podman provider is not implemented for Windows") + } } t.Setenv("DOCKER_HOST", tt.DockerHost) diff --git a/reaper_test.go b/reaper_test.go index d0b6f4fc7c..c11ad93a48 100644 --- a/reaper_test.go +++ b/reaper_test.go @@ -378,7 +378,7 @@ func Test_NewReaper(t *testing.T) { RyukConnectionTimeout: time.Minute, RyukReconnectionTimeout: 10 * time.Second, }}, - ctx: context.WithValue(context.TODO(), core.DockerHostContextKey, core.DockerSocketPathWithSchema), + ctx: context.WithValue(context.TODO(), core.DockerHostContextKey, core.DockerSocketSchema+core.DockerSocketPath), }, { name: "Reaper including custom Hub prefix", diff --git a/testing.go b/testing.go index eab23cb805..6bfb1c4f28 100644 --- a/testing.go +++ b/testing.go @@ -29,6 +29,11 @@ func SkipIfDockerDesktop(t *testing.T, ctx context.Context) { if err != nil { t.Fatalf("failed to create docker client: %s", err) } + t.Cleanup(func() { + if err := cli.Close(); err != nil { + t.Fatalf("failed to close docker client: %s", err) + } + }) info, err := cli.Info(ctx) if err != nil {