From 3f6abfab2ed510e659a75a9869fe7042634f2a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Fri, 9 May 2025 12:30:55 +0200 Subject: [PATCH 01/11] feat: add a new config to disable exposing image ports automatically --- internal/config/config.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/config/config.go b/internal/config/config.go index 64f2f7fb13..4ff98ae74b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -85,6 +85,11 @@ type Config struct { // // Environment variable: TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE TestcontainersHost string `properties:"tc.host,default="` + + // AutoExposePorts is a flag to enable or disable the automatic exposure of ports when no ports are explicitly exposed. + // + // Environment variable: TESTCONTAINERS_AUTO_EXPOSE_PORTS + AutoExposePorts bool `properties:"tc.auto.expose.ports,default=true"` } // } @@ -141,6 +146,11 @@ func read() Config { config.RyukConnectionTimeout = timeout } + autoExposePortsEnv := readTestcontainersEnv("TESTCONTAINERS_AUTO_EXPOSE_PORTS") + if parseBool(autoExposePortsEnv) { + config.AutoExposePorts = autoExposePortsEnv == "true" + } + return config } From cb9cbd9b22c3a6cd4ff952cc1f72d015105f60fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Fri, 9 May 2025 12:31:43 +0200 Subject: [PATCH 02/11] chore: support for disabling auto-exposing ports from the image It's covered by a tc property, so it's not a breaking change --- lifecycle.go | 16 +++++--- lifecycle_test.go | 94 ++++++++++++++++++++++------------------------- 2 files changed, 53 insertions(+), 57 deletions(-) diff --git a/lifecycle.go b/lifecycle.go index 72363cccca..0bb4d9a62d 100644 --- a/lifecycle.go +++ b/lifecycle.go @@ -14,6 +14,7 @@ import ( "github.com/docker/docker/api/types/network" "github.com/docker/go-connections/nat" + "github.com/testcontainers/testcontainers-go/internal/config" "github.com/testcontainers/testcontainers-go/log" ) @@ -581,12 +582,15 @@ func (p *DockerProvider) preCreateContainerHook(ctx context.Context, req Contain exposedPorts := req.ExposedPorts // this check must be done after the pre-creation Modifiers are called, so the network mode is already set if len(exposedPorts) == 0 && !hostConfig.NetworkMode.IsContainer() { - image, err := p.client.ImageInspect(ctx, dockerInput.Image) - if err != nil { - return err - } - for p := range image.Config.ExposedPorts { - exposedPorts = append(exposedPorts, string(p)) + // Only expose the ports defined in the image if configured in the Testcontainers properties file. + if config.Read().AutoExposePorts { + image, err := p.client.ImageInspect(ctx, dockerInput.Image) + if err != nil { + return err + } + for p := range image.Config.ExposedPorts { + exposedPorts = append(exposedPorts, string(p)) + } } } diff --git a/lifecycle_test.go b/lifecycle_test.go index 02de785103..41a73546c9 100644 --- a/lifecycle_test.go +++ b/lifecycle_test.go @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go/internal/config" "github.com/testcontainers/testcontainers-go/wait" ) @@ -55,7 +56,7 @@ func TestPreCreateModifierHook(t *testing.T) { require.Len(t, errs, 2) // one valid and two invalid mounts }) - t.Run("No exposed ports", func(t *testing.T) { + t.Run("no-exposed-ports", func(t *testing.T) { // reqWithModifiers { req := ContainerRequest{ Image: nginxAlpineImage, // alpine image does expose port 80 @@ -99,61 +100,52 @@ func TestPreCreateModifierHook(t *testing.T) { inputHostConfig := &container.HostConfig{} inputNetworkingConfig := &network.NetworkingConfig{} - err = provider.preCreateContainerHook(ctx, req, inputConfig, inputHostConfig, inputNetworkingConfig) - require.NoError(t, err) - // assertions - - assert.Equal( - t, - []string{"a=b"}, - inputConfig.Env, - "Docker config's env should be overwritten by the modifier", - ) - assert.Equal(t, - nat.PortSet(nat.PortSet{"80/tcp": struct{}{}}), - inputConfig.ExposedPorts, - "Docker config's exposed ports should be overwritten by the modifier", - ) - assert.Equal( - t, - []mount.Mount{ - { - Type: mount.TypeVolume, - Source: "appdata", - Target: "/data", - VolumeOptions: &mount.VolumeOptions{ - Labels: GenericLabels(), + commonAssertionFn := func(t *testing.T, inputHostConfig *container.HostConfig, inputNetworkingConfig *network.NetworkingConfig) { + require.Equal(t, []string{"a=b"}, inputConfig.Env) + require.Equal( + t, + []mount.Mount{ + { + Type: mount.TypeVolume, + Source: "appdata", + Target: "/data", + VolumeOptions: &mount.VolumeOptions{ + Labels: GenericLabels(), + }, }, }, - }, - inputHostConfig.Mounts, - "Host config's mounts should be mapped to Docker types", - ) + inputHostConfig.Mounts, + ) + require.Equal(t, []string{"b"}, inputNetworkingConfig.EndpointsConfig["a"].Aliases) + require.Equal(t, []string{"link1", "link2"}, inputNetworkingConfig.EndpointsConfig["a"].Links) + } - assert.Equal(t, nat.PortMap{ - "80/tcp": []nat.PortBinding{ - { - HostIP: "", - HostPort: "", - }, - }, - }, inputHostConfig.PortBindings, - "Host config's port bindings should be overwritten by the modifier", - ) + t.Run("auto-expose-ports-enabled", func(t *testing.T) { + t.Setenv("TESTCONTAINERS_AUTO_EXPOSE_PORTS", "true") + config.Reset() - assert.Equal( - t, - []string{"b"}, - inputNetworkingConfig.EndpointsConfig["a"].Aliases, - "Networking config's aliases should be overwritten by the modifier", - ) - assert.Equal( - t, - []string{"link1", "link2"}, - inputNetworkingConfig.EndpointsConfig["a"].Links, - "Networking config's links should be overwritten by the modifier", - ) + err = provider.preCreateContainerHook(ctx, req, inputConfig, inputHostConfig, inputNetworkingConfig) + require.NoError(t, err) + + commonAssertionFn(t, inputHostConfig, inputNetworkingConfig) + + require.Equal(t, nat.PortSet(nat.PortSet{"80/tcp": struct{}{}}), inputConfig.ExposedPorts) + require.Equal(t, nat.PortMap{"80/tcp": []nat.PortBinding{{HostIP: "", HostPort: ""}}}, inputHostConfig.PortBindings) + }) + + t.Run("auto-expose-ports-disabled", func(t *testing.T) { + t.Setenv("TESTCONTAINERS_AUTO_EXPOSE_PORTS", "false") + config.Reset() + + err = provider.preCreateContainerHook(ctx, req, inputConfig, inputHostConfig, inputNetworkingConfig) + require.NoError(t, err) + + commonAssertionFn(t, inputHostConfig, inputNetworkingConfig) + + require.Equal(t, nat.PortSet(nat.PortSet{}), inputConfig.ExposedPorts) + require.Equal(t, nat.PortMap{}, inputHostConfig.PortBindings) + }) }) t.Run("No exposed ports and network mode IsContainer", func(t *testing.T) { From 1408c810e0add33510cb43c10aa94306f6f2c8fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Fri, 9 May 2025 12:56:19 +0200 Subject: [PATCH 03/11] chore: simplify with publish-all-ports --- lifecycle.go | 8 +--- lifecycle_test.go | 109 ++++++++++++++++++++++++++++------------------ 2 files changed, 68 insertions(+), 49 deletions(-) diff --git a/lifecycle.go b/lifecycle.go index 0bb4d9a62d..059c7fad7e 100644 --- a/lifecycle.go +++ b/lifecycle.go @@ -584,13 +584,7 @@ func (p *DockerProvider) preCreateContainerHook(ctx context.Context, req Contain if len(exposedPorts) == 0 && !hostConfig.NetworkMode.IsContainer() { // Only expose the ports defined in the image if configured in the Testcontainers properties file. if config.Read().AutoExposePorts { - image, err := p.client.ImageInspect(ctx, dockerInput.Image) - if err != nil { - return err - } - for p := range image.Config.ExposedPorts { - exposedPorts = append(exposedPorts, string(p)) - } + hostConfig.PublishAllPorts = true } } diff --git a/lifecycle_test.go b/lifecycle_test.go index 41a73546c9..bcf4e54305 100644 --- a/lifecycle_test.go +++ b/lifecycle_test.go @@ -23,6 +23,54 @@ import ( "github.com/testcontainers/testcontainers-go/wait" ) +func TestPreCreateModifierHook_NoExposedPorts(t *testing.T) { + t.Run("auto-expose-ports-enabled", func(t *testing.T) { + t.Setenv("TESTCONTAINERS_AUTO_EXPOSE_PORTS", "true") + config.Reset() + + req := GenericContainerRequest{ + ContainerRequest: ContainerRequest{ + Image: nginxAlpineImage, + }, + Started: true, + } + + ctr, err := GenericContainer(context.Background(), req) + require.NoError(t, err) + CleanupContainer(t, ctr) + + json, err := ctr.Inspect(context.Background()) + require.NoError(t, err) + + require.Equal(t, nat.PortSet(nat.PortSet{"80/tcp": struct{}{}}), json.Config.ExposedPorts) + require.Equal(t, nat.PortMap{}, json.HostConfig.PortBindings) + require.NotNil(t, json.NetworkSettings.Ports["80/tcp"]) + }) + + t.Run("auto-expose-ports-disabled", func(t *testing.T) { + t.Setenv("TESTCONTAINERS_AUTO_EXPOSE_PORTS", "false") + config.Reset() + + req := GenericContainerRequest{ + ContainerRequest: ContainerRequest{ + Image: nginxAlpineImage, + }, + Started: true, + } + + ctr, err := GenericContainer(context.Background(), req) + require.NoError(t, err) + CleanupContainer(t, ctr) + + json, err := ctr.Inspect(context.Background()) + require.NoError(t, err) + + require.Equal(t, nat.PortSet(nat.PortSet{"80/tcp": struct{}{}}), json.Config.ExposedPorts) + require.Equal(t, nat.PortMap{}, json.HostConfig.PortBindings) + require.Nil(t, json.NetworkSettings.Ports["80/tcp"]) + }) +} + func TestPreCreateModifierHook(t *testing.T) { ctx := context.Background() @@ -100,52 +148,29 @@ func TestPreCreateModifierHook(t *testing.T) { inputHostConfig := &container.HostConfig{} inputNetworkingConfig := &network.NetworkingConfig{} + err = provider.preCreateContainerHook(ctx, req, inputConfig, inputHostConfig, inputNetworkingConfig) + require.NoError(t, err) + // assertions - commonAssertionFn := func(t *testing.T, inputHostConfig *container.HostConfig, inputNetworkingConfig *network.NetworkingConfig) { - require.Equal(t, []string{"a=b"}, inputConfig.Env) - require.Equal( - t, - []mount.Mount{ - { - Type: mount.TypeVolume, - Source: "appdata", - Target: "/data", - VolumeOptions: &mount.VolumeOptions{ - Labels: GenericLabels(), - }, + + require.Equal(t, []string{"a=b"}, inputConfig.Env) + require.Equal( + t, + []mount.Mount{ + { + Type: mount.TypeVolume, + Source: "appdata", + Target: "/data", + VolumeOptions: &mount.VolumeOptions{ + Labels: GenericLabels(), }, }, - inputHostConfig.Mounts, - ) - require.Equal(t, []string{"b"}, inputNetworkingConfig.EndpointsConfig["a"].Aliases) - require.Equal(t, []string{"link1", "link2"}, inputNetworkingConfig.EndpointsConfig["a"].Links) - } - - t.Run("auto-expose-ports-enabled", func(t *testing.T) { - t.Setenv("TESTCONTAINERS_AUTO_EXPOSE_PORTS", "true") - config.Reset() - - err = provider.preCreateContainerHook(ctx, req, inputConfig, inputHostConfig, inputNetworkingConfig) - require.NoError(t, err) - - commonAssertionFn(t, inputHostConfig, inputNetworkingConfig) - - require.Equal(t, nat.PortSet(nat.PortSet{"80/tcp": struct{}{}}), inputConfig.ExposedPorts) - require.Equal(t, nat.PortMap{"80/tcp": []nat.PortBinding{{HostIP: "", HostPort: ""}}}, inputHostConfig.PortBindings) - }) - - t.Run("auto-expose-ports-disabled", func(t *testing.T) { - t.Setenv("TESTCONTAINERS_AUTO_EXPOSE_PORTS", "false") - config.Reset() - - err = provider.preCreateContainerHook(ctx, req, inputConfig, inputHostConfig, inputNetworkingConfig) - require.NoError(t, err) - - commonAssertionFn(t, inputHostConfig, inputNetworkingConfig) + }, + inputHostConfig.Mounts, + ) + require.Equal(t, []string{"b"}, inputNetworkingConfig.EndpointsConfig["a"].Aliases) + require.Equal(t, []string{"link1", "link2"}, inputNetworkingConfig.EndpointsConfig["a"].Links) - require.Equal(t, nat.PortSet(nat.PortSet{}), inputConfig.ExposedPorts) - require.Equal(t, nat.PortMap{}, inputHostConfig.PortBindings) - }) }) t.Run("No exposed ports and network mode IsContainer", func(t *testing.T) { From b74cfa0452202a8d29616b98668f237af6ec95b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Fri, 9 May 2025 13:10:53 +0200 Subject: [PATCH 04/11] fix: lint --- lifecycle_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/lifecycle_test.go b/lifecycle_test.go index bcf4e54305..0d669faf41 100644 --- a/lifecycle_test.go +++ b/lifecycle_test.go @@ -170,7 +170,6 @@ func TestPreCreateModifierHook(t *testing.T) { ) require.Equal(t, []string{"b"}, inputNetworkingConfig.EndpointsConfig["a"].Aliases) require.Equal(t, []string{"link1", "link2"}, inputNetworkingConfig.EndpointsConfig["a"].Links) - }) t.Run("No exposed ports and network mode IsContainer", func(t *testing.T) { From 1fcc9261f8a19f250a443673c0f7ee8fbaff0190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Tue, 13 May 2025 17:52:28 +0200 Subject: [PATCH 05/11] fix: update tests --- internal/config/config_test.go | 54 +++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 591fcff11c..061be3000d 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -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_AUTO_EXPOSE_PORTS", "true") } func TestReadConfig(t *testing.T) { @@ -42,8 +43,9 @@ func TestReadConfig(t *testing.T) { config := Read() expected := Config{ - RyukDisabled: true, - Host: "", // docker socket is empty at the properties file + RyukDisabled: true, + Host: "", // docker socket is empty at the properties file + AutoExposePorts: true, } require.Equal(t, expected, config) @@ -66,7 +68,9 @@ func TestReadTCConfig(t *testing.T) { config := read() - expected := Config{} + expected := Config{ + AutoExposePorts: true, + } assert.Equal(t, expected, config) }) @@ -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, + AutoExposePorts: true, } assert.Equal(t, expected, config) @@ -101,7 +106,9 @@ func TestReadTCConfig(t *testing.T) { config := read() - expected := Config{} + expected := Config{ + AutoExposePorts: true, + } assert.Equal(t, expected, config) }) @@ -113,7 +120,10 @@ func TestReadTCConfig(t *testing.T) { t.Setenv("DOCKER_HOST", tcpDockerHost33293) config := read() - expected := Config{} // the config does not read DOCKER_HOST, that's why it's empty + // the config does not read DOCKER_HOST, that's why it's empty + expected := Config{ + AutoExposePorts: true, + } assert.Equal(t, expected, config) }) @@ -128,7 +138,7 @@ func TestReadTCConfig(t *testing.T) { t.Setenv("RYUK_VERBOSE", "true") t.Setenv("RYUK_RECONNECTION_TIMEOUT", "13s") t.Setenv("RYUK_CONNECTION_TIMEOUT", "12s") - + t.Setenv("TESTCONTAINERS_AUTO_EXPOSE_PORTS", "true") config := read() expected := Config{ HubImageNamePrefix: defaultHubPrefix, @@ -137,6 +147,7 @@ func TestReadTCConfig(t *testing.T) { RyukVerbose: true, RyukReconnectionTimeout: 13 * time.Second, RyukConnectionTimeout: 12 * time.Second, + AutoExposePorts: true, } assert.Equal(t, expected, config) @@ -145,9 +156,11 @@ func TestReadTCConfig(t *testing.T) { t.Run("HOME contains TC properties file", func(t *testing.T) { defaultRyukConnectionTimeout := 60 * time.Second defaultRyukReconnectionTimeout := 10 * time.Second + defaultAutoExposePorts := true defaultConfig := Config{ RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, } tests := []struct { @@ -164,6 +177,7 @@ func TestReadTCConfig(t *testing.T) { Host: tcpDockerHost33293, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -176,6 +190,7 @@ func TestReadTCConfig(t *testing.T) { Host: tcpDockerHost4711, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -191,6 +206,7 @@ func TestReadTCConfig(t *testing.T) { TLSVerify: 1, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -200,6 +216,7 @@ func TestReadTCConfig(t *testing.T) { Config{ RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -212,6 +229,7 @@ func TestReadTCConfig(t *testing.T) { Host: tcpDockerHost1234, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -222,6 +240,7 @@ func TestReadTCConfig(t *testing.T) { Host: tcpDockerHost33293, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -242,6 +261,7 @@ func TestReadTCConfig(t *testing.T) { CertPath: "/tmp/certs", RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -252,6 +272,7 @@ func TestReadTCConfig(t *testing.T) { RyukDisabled: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -262,6 +283,7 @@ func TestReadTCConfig(t *testing.T) { RyukPrivileged: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -272,6 +294,7 @@ func TestReadTCConfig(t *testing.T) { Config{ RyukReconnectionTimeout: 13 * time.Second, RyukConnectionTimeout: 12 * time.Second, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -284,6 +307,7 @@ func TestReadTCConfig(t *testing.T) { Config{ RyukReconnectionTimeout: 13 * time.Second, RyukConnectionTimeout: 12 * time.Second, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -297,6 +321,7 @@ func TestReadTCConfig(t *testing.T) { Config{ RyukReconnectionTimeout: 13 * time.Second, RyukConnectionTimeout: 12 * time.Second, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -307,6 +332,7 @@ func TestReadTCConfig(t *testing.T) { RyukVerbose: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -319,6 +345,7 @@ func TestReadTCConfig(t *testing.T) { RyukDisabled: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -331,6 +358,7 @@ func TestReadTCConfig(t *testing.T) { RyukPrivileged: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -343,6 +371,7 @@ func TestReadTCConfig(t *testing.T) { RyukDisabled: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -355,6 +384,7 @@ func TestReadTCConfig(t *testing.T) { RyukDisabled: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -383,6 +413,7 @@ func TestReadTCConfig(t *testing.T) { RyukVerbose: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -395,6 +426,7 @@ func TestReadTCConfig(t *testing.T) { RyukVerbose: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -423,6 +455,7 @@ func TestReadTCConfig(t *testing.T) { RyukPrivileged: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -435,6 +468,7 @@ func TestReadTCConfig(t *testing.T) { RyukPrivileged: true, RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -462,8 +496,9 @@ func TestReadTCConfig(t *testing.T) { "TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED": "true", }, Config{ - RyukDisabled: true, - RyukPrivileged: true, + RyukDisabled: true, + RyukPrivileged: true, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -490,6 +525,7 @@ func TestReadTCConfig(t *testing.T) { HubImageNamePrefix: defaultHubPrefix + "/props/", RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -502,6 +538,7 @@ func TestReadTCConfig(t *testing.T) { HubImageNamePrefix: defaultHubPrefix + "/env/", RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, { @@ -514,6 +551,7 @@ func TestReadTCConfig(t *testing.T) { HubImageNamePrefix: defaultHubPrefix + "/env/", RyukConnectionTimeout: defaultRyukConnectionTimeout, RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: defaultAutoExposePorts, }, }, } From c7d4e463e41734f203343c097c03fdb6421d7ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Tue, 13 May 2025 17:57:30 +0200 Subject: [PATCH 06/11] chore: add tests --- internal/config/config_test.go | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 061be3000d..9d29a0bf5c 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -554,6 +554,54 @@ func TestReadTCConfig(t *testing.T) { AutoExposePorts: defaultAutoExposePorts, }, }, + { + "auto-expose-ports/env-var/properties/0", + `tc.auto.expose.ports=true`, + map[string]string{ + "TESTCONTAINERS_AUTO_EXPOSE_PORTS": "true", + }, + Config{ + RyukConnectionTimeout: defaultRyukConnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: true, + }, + }, + { + "auto-expose-ports/env-var/properties/1", + `tc.auto.expose.ports=false`, + map[string]string{ + "TESTCONTAINERS_AUTO_EXPOSE_PORTS": "true", + }, + Config{ + RyukConnectionTimeout: defaultRyukConnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: true, + }, + }, + { + "auto-expose-ports/env-var/properties/2", + `tc.auto.expose.ports=true`, + map[string]string{ + "TESTCONTAINERS_AUTO_EXPOSE_PORTS": "false", + }, + Config{ + RyukConnectionTimeout: defaultRyukConnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: false, + }, + }, + { + "auto-expose-ports/env-var/properties/3", + `tc.auto.expose.ports=false`, + map[string]string{ + "TESTCONTAINERS_AUTO_EXPOSE_PORTS": "false", + }, + Config{ + RyukConnectionTimeout: defaultRyukConnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + AutoExposePorts: false, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 959c70d4f8d66d4143278020b091e5ccfd3e8c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Tue, 13 May 2025 19:03:05 +0200 Subject: [PATCH 07/11] chore: reset config in tests --- docker_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docker_test.go b/docker_test.go index 652d0fcdbb..a5497ab221 100644 --- a/docker_test.go +++ b/docker_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go/internal/config" "github.com/testcontainers/testcontainers-go/internal/core" "github.com/testcontainers/testcontainers-go/log" "github.com/testcontainers/testcontainers-go/wait" @@ -93,6 +94,9 @@ func TestContainerWithHostNetworkOptions(t *testing.T) { } func TestContainerWithHostNetworkOptions_UseExposePortsFromImageConfigs(t *testing.T) { + t.Setenv("TESTCONTAINERS_AUTO_EXPOSE_PORTS", "true") + config.Reset() + ctx := context.Background() gcr := GenericContainerRequest{ ContainerRequest: ContainerRequest{ @@ -1907,6 +1911,9 @@ func assertExtractedFiles(t *testing.T, ctx context.Context, container Container } func TestDockerProviderFindContainerByName(t *testing.T) { + t.Setenv("TESTCONTAINERS_AUTO_EXPOSE_PORTS", "true") + config.Reset() + ctx := context.Background() provider, err := NewDockerProvider(WithLogger(log.TestLogger(t))) require.NoError(t, err) From 2b5193fa54f014bb66bbf652dd75b46b4cdacb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Mon, 19 May 2025 13:39:24 +0200 Subject: [PATCH 08/11] chore: log the usage of the deprecated behaviour as a property --- internal/config/config.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/config/config.go b/internal/config/config.go index 4ff98ae74b..f43a68f84a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -9,6 +9,8 @@ import ( "time" "github.com/magiconair/properties" + + "github.com/testcontainers/testcontainers-go/log" ) const ReaperDefaultImage = "testcontainers/ryuk:0.11.0" @@ -151,6 +153,10 @@ func read() Config { config.AutoExposePorts = autoExposePortsEnv == "true" } + if config.AutoExposePorts { + log.Printf("⚠️ Testcontainers is configured to automatically expose ports from the Image definition, but this is deprecated and will be removed in a future version. Please set `tc.auto.expose.ports=false` in the testcontainers.properties file.") + } + return config } From 7a8d9a40e5aaa5e29fe0358beff4debfb77541be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 15 May 2025 22:50:51 +0200 Subject: [PATCH 09/11] feat: support adding wait strategies as functional option (#3161) * feat: add a new functional option to append wait strategies to modules * chore: update modules * docs: make it mkore clear --- docs/features/common_functional_options.md | 6 +- docs/modules/index.md | 6 +- modules/k3s/k3s_test.go | 2 +- modules/localstack/examples_test.go | 2 +- modules/mongodb/examples_test.go | 2 - modules/mssql/mssql_test.go | 4 +- modules/postgres/examples_test.go | 7 +- modules/postgres/postgres_test.go | 10 +- modules/postgres/wait_strategies.go | 2 +- options.go | 27 ++++- options_test.go | 122 +++++++++++++++++++++ 11 files changed, 166 insertions(+), 24 deletions(-) diff --git a/docs/features/common_functional_options.md b/docs/features/common_functional_options.md index 7acebf12e0..32be5e50dd 100644 --- a/docs/features/common_functional_options.md +++ b/docs/features/common_functional_options.md @@ -242,12 +242,14 @@ You can also use `testcontainers.WithAdditionalLifecycleHooks`, which appends th #### Wait Strategies -If you need to set a different wait strategy for the container, you can use `testcontainers.WithWaitStrategy` with a valid wait strategy. +If you need to set a different wait strategy for the container, replacing the existing one, you can use `testcontainers.WithWaitStrategy` with a valid wait strategy. !!!info The default deadline for the wait strategy is 60 seconds. -At the same time, it's possible to set a wait strategy and a custom deadline with `testcontainers.WithWaitStrategyAndDeadline`. +At the same time, it's possible to replace the wait strategy with a new one and a custom deadline, using `testcontainers.WithWaitStrategyAndDeadline`. + +Finally, you can also append a wait strategy to the existing wait strategy, using `testcontainers.WithAdditionalWaitStrategy` and `testcontainers.WithAdditionalWaitStrategyAndDeadline`. #### Startup Commands diff --git a/docs/modules/index.md b/docs/modules/index.md index 046f05416b..6f0edadc08 100644 --- a/docs/modules/index.md +++ b/docs/modules/index.md @@ -210,8 +210,10 @@ In order to simplify the creation of the container for a given module, `Testcont - `testcontainers.WithAdditionalLifecycleHooks`: a function that appends lifecycle hooks to the existing ones for the container request. - `testcontainers.WithAlwaysPull`: a function that pulls the image before starting the container. - `testcontainers.WithImagePlatform`: a function that sets the image platform for the container request. -- `testcontainers.WithWaitStrategy`: a function that sets the wait strategy for the container request. -- `testcontainers.WithWaitStrategyAndDeadline`: a function that sets the wait strategy for the container request with a deadline. +- `testcontainers.WithWaitStrategy`: a function that replaces the wait strategy for the container request. +- `testcontainers.WithAdditionalWaitStrategy`: a function that appends the wait strategy for the container request. +- `testcontainers.WithWaitStrategyAndDeadline`: a function that replaces the wait strategy for the container request with a deadline. +- `testcontainers.WithAdditionalWaitStrategyAndDeadline`: a function that appends the wait strategy for the container request with a deadline. - `testcontainers.WithStartupCommand`: a function that sets the execution of a command when the container starts. - `testcontainers.WithAfterReadyCommand`: a function that sets the execution of a command right after the container is ready (its wait strategy is satisfied). - `testcontainers.WithDockerfile`: a function that sets the build from a Dockerfile for the container request. diff --git a/modules/k3s/k3s_test.go b/modules/k3s/k3s_test.go index d89121dde3..3328a56d3d 100644 --- a/modules/k3s/k3s_test.go +++ b/modules/k3s/k3s_test.go @@ -146,7 +146,7 @@ func Test_WithManifestOption(t *testing.T) { k3sContainer, err := k3s.Run(ctx, "rancher/k3s:v1.27.1-k3s1", k3s.WithManifest("nginx-manifest.yaml"), - testcontainers.WithWaitStrategy(wait.ForExec([]string{"kubectl", "wait", "pod", "nginx", "--for=condition=Ready"})), + testcontainers.WithAdditionalWaitStrategy(wait.ForExec([]string{"kubectl", "wait", "pod", "nginx", "--for=condition=Ready"})), ) testcontainers.CleanupContainer(t, k3sContainer) require.NoError(t, err) diff --git a/modules/localstack/examples_test.go b/modules/localstack/examples_test.go index d67475286a..b2e6e877ab 100644 --- a/modules/localstack/examples_test.go +++ b/modules/localstack/examples_test.go @@ -101,7 +101,7 @@ func ExampleRun_legacyMode() { ctx, "localstack/localstack:0.10.0", testcontainers.WithEnv(map[string]string{"SERVICES": "s3,sqs"}), - testcontainers.WithWaitStrategy(wait.ForLog("Ready.").WithStartupTimeout(5*time.Minute).WithOccurrence(1)), + testcontainers.WithAdditionalWaitStrategy(wait.ForLog("Ready.").WithStartupTimeout(5*time.Minute).WithOccurrence(1)), ) defer func() { if err := testcontainers.TerminateContainer(ctr); err != nil { diff --git a/modules/mongodb/examples_test.go b/modules/mongodb/examples_test.go index 98a31d61fa..c129bb3645 100644 --- a/modules/mongodb/examples_test.go +++ b/modules/mongodb/examples_test.go @@ -11,7 +11,6 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mongodb" - "github.com/testcontainers/testcontainers-go/wait" ) func ExampleRun() { @@ -89,7 +88,6 @@ func ExampleRun_withCredentials() { "mongo:6", mongodb.WithUsername("root"), mongodb.WithPassword("password"), - testcontainers.WithWaitStrategy(wait.ForLog("Waiting for connections")), ) defer func() { if err := testcontainers.TerminateContainer(ctr); err != nil { diff --git a/modules/mssql/mssql_test.go b/modules/mssql/mssql_test.go index 1b5901d6d5..10052b8d40 100644 --- a/modules/mssql/mssql_test.go +++ b/modules/mssql/mssql_test.go @@ -50,7 +50,7 @@ func TestMSSQLServerWithMissingEulaOption(t *testing.T) { t.Run("empty", func(t *testing.T) { ctr, err := mssql.Run(ctx, "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04", - testcontainers.WithWaitStrategy( + testcontainers.WithAdditionalWaitStrategy( wait.ForLog("The SQL Server End-User License Agreement (EULA) must be accepted")), ) testcontainers.CleanupContainer(t, ctr) @@ -61,7 +61,7 @@ func TestMSSQLServerWithMissingEulaOption(t *testing.T) { ctr, err := mssql.Run(ctx, "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04", testcontainers.WithEnv(map[string]string{"ACCEPT_EULA": "yes"}), - testcontainers.WithWaitStrategy( + testcontainers.WithAdditionalWaitStrategy( wait.ForLog("The SQL Server End-User License Agreement (EULA) must be accepted")), ) testcontainers.CleanupContainer(t, ctr) diff --git a/modules/postgres/examples_test.go b/modules/postgres/examples_test.go index 1f5b0e1c86..213691c9fe 100644 --- a/modules/postgres/examples_test.go +++ b/modules/postgres/examples_test.go @@ -5,11 +5,9 @@ import ( "fmt" "log" "path/filepath" - "time" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/postgres" - "github.com/testcontainers/testcontainers-go/wait" ) func ExampleRun() { @@ -27,10 +25,7 @@ func ExampleRun() { postgres.WithDatabase(dbName), postgres.WithUsername(dbUser), postgres.WithPassword(dbPassword), - testcontainers.WithWaitStrategy( - wait.ForLog("database system is ready to accept connections"). - WithOccurrence(2). - WithStartupTimeout(5*time.Second)), + postgres.BasicWaitStrategies(), ) defer func() { if err := testcontainers.TerminateContainer(postgresContainer); err != nil { diff --git a/modules/postgres/postgres_test.go b/modules/postgres/postgres_test.go index a389d68bff..fea3964ae6 100644 --- a/modules/postgres/postgres_test.go +++ b/modules/postgres/postgres_test.go @@ -155,7 +155,7 @@ func TestContainerWithWaitForSQL(t *testing.T) { postgres.WithDatabase(dbname), postgres.WithUsername(user), postgres.WithPassword(password), - testcontainers.WithWaitStrategy(wait.ForSQL(nat.Port(port), "postgres", dbURL)), + testcontainers.WithAdditionalWaitStrategy(wait.ForSQL(nat.Port(port), "postgres", dbURL)), ) testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) @@ -168,7 +168,7 @@ func TestContainerWithWaitForSQL(t *testing.T) { postgres.WithDatabase(dbname), postgres.WithUsername(user), postgres.WithPassword(password), - testcontainers.WithWaitStrategy(wait.ForSQL(nat.Port(port), "postgres", dbURL).WithStartupTimeout(time.Second*5).WithQuery("SELECT 10")), + testcontainers.WithAdditionalWaitStrategy(wait.ForSQL(nat.Port(port), "postgres", dbURL).WithStartupTimeout(time.Second*5).WithQuery("SELECT 10")), ) testcontainers.CleanupContainer(t, ctr) require.NoError(t, err) @@ -181,7 +181,7 @@ func TestContainerWithWaitForSQL(t *testing.T) { postgres.WithDatabase(dbname), postgres.WithUsername(user), postgres.WithPassword(password), - testcontainers.WithWaitStrategy(wait.ForSQL(nat.Port(port), "postgres", dbURL).WithStartupTimeout(time.Second*5).WithQuery("SELECT 'a' from b")), + testcontainers.WithAdditionalWaitStrategy(wait.ForSQL(nat.Port(port), "postgres", dbURL).WithStartupTimeout(time.Second*5).WithQuery("SELECT 'a' from b")), ) testcontainers.CleanupContainer(t, ctr) require.Error(t, err) @@ -225,7 +225,7 @@ func TestWithSSL(t *testing.T) { postgres.WithDatabase(dbname), postgres.WithUsername(user), postgres.WithPassword(password), - testcontainers.WithWaitStrategy(wait.ForLog("database system is ready to accept connections").WithOccurrence(2).WithStartupTimeout(5*time.Second)), + testcontainers.WithAdditionalWaitStrategy(wait.ForLog("database system is ready to accept connections").WithOccurrence(2).WithStartupTimeout(5*time.Second)), postgres.WithSSLCert(caCert.CertPath, serverCerts.CertPath, serverCerts.KeyPath), ) @@ -255,7 +255,7 @@ func TestSSLValidatesKeyMaterialPath(t *testing.T) { postgres.WithDatabase(dbname), postgres.WithUsername(user), postgres.WithPassword(password), - testcontainers.WithWaitStrategy(wait.ForLog("database system is ready to accept connections").WithOccurrence(2).WithStartupTimeout(5*time.Second)), + testcontainers.WithAdditionalWaitStrategy(wait.ForLog("database system is ready to accept connections").WithOccurrence(2).WithStartupTimeout(5*time.Second)), postgres.WithSSLCert("", "", ""), ) diff --git a/modules/postgres/wait_strategies.go b/modules/postgres/wait_strategies.go index e748f4cc3a..92dc3f6ec6 100644 --- a/modules/postgres/wait_strategies.go +++ b/modules/postgres/wait_strategies.go @@ -14,7 +14,7 @@ import ( // Without this, the tests will be flaky on those OSes! func BasicWaitStrategies() testcontainers.CustomizeRequestOption { // waitStrategy { - return testcontainers.WithWaitStrategy( + return testcontainers.WithAdditionalWaitStrategy( // First, we wait for the container to log readiness twice. // This is because it will restart itself after the first startup. wait.ForLog("database system is ready to accept connections").WithOccurrence(2), diff --git a/options.go b/options.go index 81ce4c2b68..98f4b57633 100644 --- a/options.go +++ b/options.go @@ -373,12 +373,17 @@ func WithAfterReadyCommand(execs ...Executable) CustomizeRequestOption { } } -// WithWaitStrategy sets the wait strategy for a container, using 60 seconds as deadline +// WithWaitStrategy replaces the wait strategy for a container, using 60 seconds as deadline func WithWaitStrategy(strategies ...wait.Strategy) CustomizeRequestOption { return WithWaitStrategyAndDeadline(60*time.Second, strategies...) } -// WithWaitStrategyAndDeadline sets the wait strategy for a container, including deadline +// WithAdditionalWaitStrategy appends the wait strategy for a container, using 60 seconds as deadline +func WithAdditionalWaitStrategy(strategies ...wait.Strategy) CustomizeRequestOption { + return WithAdditionalWaitStrategyAndDeadline(60*time.Second, strategies...) +} + +// WithWaitStrategyAndDeadline replaces the wait strategy for a container, including deadline func WithWaitStrategyAndDeadline(deadline time.Duration, strategies ...wait.Strategy) CustomizeRequestOption { return func(req *GenericContainerRequest) error { req.WaitingFor = wait.ForAll(strategies...).WithDeadline(deadline) @@ -387,6 +392,24 @@ func WithWaitStrategyAndDeadline(deadline time.Duration, strategies ...wait.Stra } } +// WithAdditionalWaitStrategyAndDeadline appends the wait strategy for a container, including deadline +func WithAdditionalWaitStrategyAndDeadline(deadline time.Duration, strategies ...wait.Strategy) CustomizeRequestOption { + return func(req *GenericContainerRequest) error { + if req.WaitingFor == nil { + req.WaitingFor = wait.ForAll(strategies...).WithDeadline(deadline) + return nil + } + + wss := make([]wait.Strategy, 0, len(strategies)+1) + wss = append(wss, req.WaitingFor) + wss = append(wss, strategies...) + + req.WaitingFor = wait.ForAll(wss...).WithDeadline(deadline) + + return nil + } +} + // WithImageMount mounts an image to a container, passing the source image name, // the relative subpath to mount in that image, and the mount point in the target container. // This option validates that the subpath is a relative path, raising an error otherwise. diff --git a/options_test.go b/options_test.go index 69ae12d583..21075ea750 100644 --- a/options_test.go +++ b/options_test.go @@ -4,6 +4,7 @@ import ( "context" "io" "testing" + "time" "github.com/stretchr/testify/require" @@ -784,3 +785,124 @@ func TestWithNoStart(t *testing.T) { require.NoError(t, err) require.False(t, req.Started) } + +func TestWithWaitStrategy(t *testing.T) { + testDuration := 10 * time.Second + defaultDuration := 60 * time.Second + + waitForFoo := wait.ForLog("foo") + waitForBar := wait.ForLog("bar") + + testWaitFor := func(t *testing.T, replace bool, customDuration *time.Duration, initial wait.Strategy, add wait.Strategy, expected wait.Strategy) { + t.Helper() + + req := &testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + WaitingFor: initial, + }, + } + + var opt testcontainers.CustomizeRequestOption + if replace { + opt = testcontainers.WithWaitStrategy(add) + if customDuration != nil { + opt = testcontainers.WithWaitStrategyAndDeadline(*customDuration, add) + } + } else { + opt = testcontainers.WithAdditionalWaitStrategy(add) + if customDuration != nil { + opt = testcontainers.WithAdditionalWaitStrategyAndDeadline(*customDuration, add) + } + } + require.NoError(t, opt.Customize(req)) + require.Equal(t, expected, req.WaitingFor) + } + + t.Run("replace-nil", func(t *testing.T) { + t.Run("default-duration", func(t *testing.T) { + testWaitFor(t, + true, + nil, + nil, + waitForFoo, + wait.ForAll(waitForFoo).WithDeadline(defaultDuration), + ) + }) + + t.Run("custom-duration", func(t *testing.T) { + testWaitFor(t, + true, + &testDuration, + nil, + waitForFoo, + wait.ForAll(waitForFoo).WithDeadline(testDuration), + ) + }) + }) + + t.Run("replace-existing", func(t *testing.T) { + t.Run("default-duration", func(t *testing.T) { + testWaitFor(t, + true, + nil, + waitForFoo, + waitForBar, + wait.ForAll(waitForBar).WithDeadline(defaultDuration), + ) + }) + + t.Run("custom-duration", func(t *testing.T) { + testWaitFor(t, + true, + &testDuration, + waitForFoo, + waitForBar, + wait.ForAll(waitForBar).WithDeadline(testDuration), + ) + }) + }) + + t.Run("add-to-nil", func(t *testing.T) { + t.Run("default-duration", func(t *testing.T) { + testWaitFor(t, + false, + nil, + nil, + waitForFoo, + wait.ForAll(waitForFoo).WithDeadline(defaultDuration), + ) + }) + + t.Run("custom-duration", func(t *testing.T) { + testWaitFor(t, + false, + &testDuration, + nil, + waitForFoo, + wait.ForAll(waitForFoo).WithDeadline(testDuration), + ) + }) + }) + + t.Run("add-to-existing", func(t *testing.T) { + t.Run("default-duration", func(t *testing.T) { + testWaitFor(t, + false, + nil, + waitForFoo, + waitForBar, + wait.ForAll(waitForFoo, waitForBar).WithDeadline(defaultDuration), + ) + }) + + t.Run("custom-duration", func(t *testing.T) { + testWaitFor(t, + false, + &testDuration, + waitForFoo, + waitForBar, + wait.ForAll(waitForFoo, waitForBar).WithDeadline(testDuration), + ) + }) + }) +} From 82400cc53fe7f3e3e60a771b622a411107d62930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Fri, 16 May 2025 07:12:31 +0200 Subject: [PATCH 10/11] chore(ci): do not fail fast in the Testcontainers Cloud run (#3164) --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1c02d042a..a036d17803 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,8 @@ jobs: - lint name: "Test using Testcontainers Cloud" strategy: + # We don't want to fail the build the soonest but identify which modules passed and failed. + fail-fast: false matrix: go-version: [1.23.x, 1.24.x] uses: ./.github/workflows/ci-test-go.yml From 62adfbae1a77bfa1012cd4a6b56799b95dc19201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Fri, 16 May 2025 15:14:51 +0200 Subject: [PATCH 11/11] fix(ci): do not run sonar for Testcontainers Cloud (#3166) --- .github/workflows/ci-test-go.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-test-go.yml b/.github/workflows/ci-test-go.yml index 3b981f9f9f..ab698022f6 100644 --- a/.github/workflows/ci-test-go.yml +++ b/.github/workflows/ci-test-go.yml @@ -120,6 +120,7 @@ jobs: run: | if [[ "1.23.x" == "${{ inputs.go-version }}" ]] && \ [[ "true" != "${{ inputs.rootless-docker }}" ]] && \ + [[ "true" != "${{ inputs.testcontainers-cloud }}" ]] && \ [[ "true" != "${{ inputs.ryuk-disabled }}" ]] && \ [[ "main" == "${{ github.ref_name }}" ]] && \ [[ "testcontainers" == "${{ github.repository_owner }}" ]]; then