fix: setup ExposeHostPorts forward on container start hook#3200
fix: setup ExposeHostPorts forward on container start hook#3200mdelapenya wants to merge 14 commits intotestcontainers:mainfrom
Conversation
Fixes testcontainers#2811 Previously ExposedHostPorts would start an SSHD container prior to starting the testcontainer and inject a PostReadies lifecycle hook into the testcontainer in order to set up remote port forwarding from the host to the SSHD container so the testcontainer can talk to the host via the SSHD container This would be an issue if the testcontainer depends on accessing the host port on startup ( e.g., a proxy server ) as the forwarding for the host access isn't set up until all the WiatFor strategies on the testcontainer have completed. The fix is to move the forwarding setup to the PreCreates hook on the testcontainer. Since remote forwarding doesn't establish a connection to the host port until a connection is made to the remote port, this should not be an issue even if the host isn't listening yet and ensures the remote port is available to the testcontainer immediately.
* main: (236 commits) feat(kafka,redpanda): support for waiting for mapped ports without external checks (testcontainers#3165) chore: bump ryuk to 0.12.0 (testcontainers#3195) feat!: add options when creating RawCommand (testcontainers#3168) chore(deps)!: bump github.com/docker/docker from 28.1.1+incompatible to 28.2.2+incompatible (testcontainers#3194) feat(couchbase): adding auth to couchbase initCluster functions to support container reuse (testcontainers#3048) chore(deps): bump github.com/containerd/containerd/v2 (testcontainers#3167) docs(options): refactor options layout in modules (testcontainers#3163) fix(ci): do not run sonar for Testcontainers Cloud (testcontainers#3166) chore(ci): do not fail fast in the Testcontainers Cloud run (testcontainers#3164) feat: support adding wait strategies as functional option (testcontainers#3161) fix(etcd): expose ports for the etcd nodes (testcontainers#3162) fix(wait): no port to wait for (testcontainers#3158) feat: add more functional options for customising containers (testcontainers#3156) docs(redpanda): update sasl authentication option to use scram sha 256 (testcontainers#3126) chore(deps): bump mkdocs-include-markdown-plugin from 6.2.2 to 7.1.5 (testcontainers#3137) chore(deps): bump github.com/shirou/gopsutil/v4 from 4.25.1 to 4.25.4 (testcontainers#3133) chore(deps): bump github.com/docker/docker from 28.0.1+incompatible to 28.1.1+incompatible (testcontainers#3152) feat(memcached): add memcached module (testcontainers#3132) fix(etcd): single node etcd cluster access (testcontainers#3149) feat(valkey): add TLS support for Valkey (testcontainers#3131) ...
✅ Deploy Preview for testcontainers-go ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
* main: (30 commits) fix: preserve unix socket schema in testcontainers host from properties (testcontainers#3213) feat(registry): add helper functions to pull and tag images (testcontainers#3275) fix(reaper): remove termSignal override (testcontainers#3261) chore(deps): bump ryuk to v0.13.0, which uses scratch as base image (testcontainers#3274) chore(release): refine release script to update inter-module dependencies (testcontainers#3273) fix(registry): update `WithHtpasswd` to use `os.CreateTemp` instead of `os.Create` with `filepath.Join`. (testcontainers#3272) chore(deps): bump github.com/docker/docker from 28.2.2+incompatible to 28.3.3+incompatible (testcontainers#3270) chore(postgres): use require.NotNil instead of assert.NotNil (testcontainers#3252) fix(nats): use wait for listening port instead of wait for log (testcontainers#3256) chore(deps): bump github.com/go-viper/mapstructure/v2 (testcontainers#3267) fix(postgres): snapshot restore (testcontainers#3264) fix(dockermcpgateway): use duckduckgo instead of brave (testcontainers#3247) feat: add Solace pubsub+ module (testcontainers#3230) feat(options): add WithProvider (testcontainers#3241) chore(deps): bump github/codeql-action from 3.29.2 to 3.29.3 (testcontainers#3237) chore(deps): bump golang.org/x/oauth2 in /modules/weaviate (testcontainers#3240) chore(deps): bump mkdocs-include-markdown-plugin from 7.1.5 to 7.1.6 (testcontainers#3239) chore(deps): bump requests from 2.32.0 to 2.32.4 (testcontainers#3204) feat(mcpgateway): add MCP gateway module (testcontainers#3232) chore(deps): bump golang.org/x/oauth2 in /modules/pulsar (testcontainers#3236) ...
|
When is this going to be merged @mdelapenya @stevenh ? |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Summary by CodeRabbit
WalkthroughSSH tunnel setup for HostAccessPorts moved from container-readiness (PostReadies) to container-creation (PreCreates) phase, enabling host port forwarding before container startup. Callback signature updated to accept ContainerRequest instead of Container. Tests enhanced with per-port forwarding verification. Solace module wait strategy timeouts increased. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~18 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip You can enable review details to help with troubleshooting, context usage and more.Enable the |
|
@kberezin-nshl my bad, this PR was piled in the list. Thanks for the ping 🙏 |
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
port_forwarding.go(1 hunks)port_forwarding_test.go(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-29T13:57:14.636Z
Learnt from: mdelapenya
Repo: testcontainers/testcontainers-go PR: 3319
File: modules/arangodb/arangodb.go:46-57
Timestamp: 2025-09-29T13:57:14.636Z
Learning: In testcontainers-go ArangoDB module, the wait strategy combines port listening check with HTTP readiness check using wait.ForAll - both strategies are required and complementary, not redundant.
Applied to files:
port_forwarding_test.go
📚 Learning: 2025-09-29T15:08:18.694Z
Learnt from: mdelapenya
Repo: testcontainers/testcontainers-go PR: 3320
File: modules/artemis/artemis.go:98-103
Timestamp: 2025-09-29T15:08:18.694Z
Learning: In testcontainers-go, nat.Port is a type alias for string, so untyped string constants can be passed directly to functions expecting nat.Port (like wait.ForListeningPort) without explicit type conversion - the Go compiler handles the implicit conversion automatically.
Applied to files:
port_forwarding_test.go
🧬 Code graph analysis (2)
port_forwarding.go (2)
lifecycle.go (2)
ContainerLifecycleHooks(43-55)ContainerRequestHook(24-24)container.go (1)
ContainerRequest(131-171)
port_forwarding_test.go (3)
wait/wait.go (1)
Strategy(17-19)wait/exec.go (1)
ForExec(72-74)port_forwarding.go (1)
HostInternal(28-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: lint (modules/cassandra) / lint: modules/cassandra
- GitHub Check: lint (modules/artemis) / lint: modules/artemis
- GitHub Check: lint (modules/grafana-lgtm) / lint: modules/grafana-lgtm
- GitHub Check: lint (modules/aerospike) / lint: modules/aerospike
- GitHub Check: lint (modules/solace) / lint: modules/solace
- GitHub Check: lint (modules/mockserver) / lint: modules/mockserver
- GitHub Check: lint / lint:
- GitHub Check: lint (modules/meilisearch) / lint: modules/meilisearch
- GitHub Check: lint (modules/mongodb) / lint: modules/mongodb
- GitHub Check: lint (modules/surrealdb) / lint: modules/surrealdb
- GitHub Check: lint (modules/dynamodb) / lint: modules/dynamodb
- GitHub Check: lint (modules/couchbase) / lint: modules/couchbase
- GitHub Check: lint (modules/azurite) / lint: modules/azurite
- GitHub Check: lint (modules/mssql) / lint: modules/mssql
- GitHub Check: lint (modules/k3s) / lint: modules/k3s
- GitHub Check: lint (modules/elasticsearch) / lint: modules/elasticsearch
- GitHub Check: lint (modules/gcloud) / lint: modules/gcloud
- GitHub Check: lint (modules/postgres) / lint: modules/postgres
- GitHub Check: lint (modules/cockroachdb) / lint: modules/cockroachdb
- GitHub Check: Analyze (go)
🔇 Additional comments (2)
port_forwarding.go (1)
163-173: LGTM! Lifecycle hook change correctly fixes the issue.Moving SSH tunnel setup from
PostReadiestoPreCreatesensures host ports are accessible during container startup and wait strategies. The hook signature change fromContainerHooktoContainerRequestHookis correct, andreq.HostAccessPortsis available at this lifecycle stage. Since SSH remote port forwarding (line 316) only establishes the listener and doesn't require an active host connection until traffic arrives, this earlier setup aligns with the PR objectives.port_forwarding_test.go (1)
42-51: Wait strategies are never used—test doesn't verify the fix.The wait strategies verify that host ports are accessible during container startup (the core behavior this PR fixes), but they are never applied to any container request. Without these wait strategies, the test cannot confirm that ports are reachable before the container becomes ready.
Add the wait strategies to the container request. For example, in
testExposeHostPortsor pass them as a parameter and combine them withwait.ForAll:func testExposeHostPorts(t *testing.T, hostPorts []int, waitStrats []wait.Strategy, hasNetwork, hasHostAccess bool) { // ... existing code ... opts := []testcontainers.ContainerCustomizer{ testcontainers.WithHostPortAccess(hostAccessPorts...), testcontainers.WithCmd("top"), } if hasHostAccess && len(waitStrats) > 0 { opts = append(opts, testcontainers.WithWaitStrategy(wait.ForAll(waitStrats...))) } // ... rest of function }Then update the test calls to pass the wait strategies, e.g.:
testExposeHostPorts(t, singlePort, waitStrategies[0:1], false, false)⛔ Skipped due to learnings
Learnt from: mdelapenya Repo: testcontainers/testcontainers-go PR: 3319 File: modules/arangodb/arangodb.go:46-57 Timestamp: 2025-09-29T13:57:14.636Z Learning: In testcontainers-go ArangoDB module, the wait strategy combines port listening check with HTTP readiness check using wait.ForAll - both strategies are required and complementary, not redundant.Learnt from: mdelapenya Repo: testcontainers/testcontainers-go PR: 3320 File: modules/artemis/artemis.go:98-103 Timestamp: 2025-09-29T15:08:18.694Z Learning: In testcontainers-go, nat.Port is a type alias for string, so untyped string constants can be passed directly to functions expecting nat.Port (like wait.ForListeningPort) without explicit type conversion - the Go compiler handles the implicit conversion automatically.
* main: fix(kafka): strip architecture suffix from Kafka image tags for semver parsing (testcontainers#3276) chore(deps): bump github.com/dvsekhvalnov/jose2go in /modules/pulsar (testcontainers#3491)
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
port_forwarding_test.go(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-29T15:08:18.694Z
Learnt from: mdelapenya
Repo: testcontainers/testcontainers-go PR: 3320
File: modules/artemis/artemis.go:98-103
Timestamp: 2025-09-29T15:08:18.694Z
Learning: In testcontainers-go, nat.Port is a type alias for string, so untyped string constants can be passed directly to functions expecting nat.Port (like wait.ForListeningPort) without explicit type conversion - the Go compiler handles the implicit conversion automatically.
Applied to files:
port_forwarding_test.go
📚 Learning: 2025-09-29T13:57:14.636Z
Learnt from: mdelapenya
Repo: testcontainers/testcontainers-go PR: 3319
File: modules/arangodb/arangodb.go:46-57
Timestamp: 2025-09-29T13:57:14.636Z
Learning: In testcontainers-go ArangoDB module, the wait strategy combines port listening check with HTTP readiness check using wait.ForAll - both strategies are required and complementary, not redundant.
Applied to files:
port_forwarding_test.go
🧬 Code graph analysis (1)
port_forwarding_test.go (3)
wait/wait.go (1)
Strategy(17-19)wait/exec.go (1)
ForExec(72-74)port_forwarding.go (1)
HostInternal(28-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
- GitHub Check: Redirect rules - testcontainers-go
- GitHub Check: Header rules - testcontainers-go
- GitHub Check: Pages changed - testcontainers-go
- GitHub Check: lint (modules/registry) / lint: modules/registry
- GitHub Check: lint (modules/meilisearch) / lint: modules/meilisearch
- GitHub Check: lint (modules/dockermodelrunner) / lint: modules/dockermodelrunner
- GitHub Check: lint (modules/azurite) / lint: modules/azurite
- GitHub Check: lint (modules/dynamodb) / lint: modules/dynamodb
- GitHub Check: lint (modules/qdrant) / lint: modules/qdrant
- GitHub Check: lint (modules/mssql) / lint: modules/mssql
- GitHub Check: lint (modules/nats) / lint: modules/nats
- GitHub Check: lint (modules/mongodb) / lint: modules/mongodb
- GitHub Check: lint (modules/postgres) / lint: modules/postgres
- GitHub Check: Analyze (go)
| "github.com/testcontainers/testcontainers-go/wait" | ||
| ) | ||
|
|
||
| const ( | ||
| expectedResponse = "Hello, World!" | ||
| ) | ||
|
|
||
| func TestExposeHostPorts(t *testing.T) { | ||
| hostPorts := make([]int, 3) | ||
| const numberOfPorts = 3 | ||
|
|
||
| servers := make([]*httptest.Server, numberOfPorts) | ||
| hostPorts := make([]int, numberOfPorts) | ||
| waitStrategies := make([]wait.Strategy, numberOfPorts) | ||
|
|
There was a problem hiding this comment.
New waitStrategies are never used; clarify intent or remove to avoid dead code
The waitStrategies slice is fully populated with wait.ForExec(...).WithExitCodeMatcher(...).WithResponseMatcher(...), but it’s never read or passed into testExposeHostPorts / container options. As written, the new wait logic has no effect, and the tests still only validate host access via the explicit containerHasHostAccess / containerHasNoHostAccess calls after the container is started.
Similarly, the servers slice is only written to and never read, so it’s functionally redundant right now.
Depending on your intent:
- If these strategies are meant to assert that host ports are reachable during container startup (per the PR description), you likely want to plumb them into the container’s waiting strategy (e.g., via a
WaitingFor/ wait customization) and possibly thread them intotestExposeHostPorts. - If they’re not required, consider removing
waitStrategies(and thewaitimport) and possibly the unusedserversslice to keep the test simple and avoid confusion.
Also applies to: 37-49
🤖 Prompt for AI Agents
In port_forwarding_test.go around lines 18 to 31 (and similarly 37 to 49), the
slices waitStrategies and servers are built but never used; either wire them
into the container waiting logic or remove them — to fix quickly, delete the
wait import, the waitStrategies and servers declarations and all code that
populates them (including the per-port wait.ForExec(...) setup), and remove any
related unused variables so the test only keeps hostPorts and the actual
assertions; if you prefer to use them instead, pass the corresponding
wait.Strategy into the container creation (e.g., via
WaitingFor/WithStartupTimeout or by extending testExposeHostPorts to accept and
apply a per-port wait strategy) and ensure servers is read where its
httptest.Servers are needed.
|
Any updates here? 🙏 |
Fixes #2811
Previously ExposedHostPorts would start an SSHD container prior to
starting the testcontainer and inject a PostReadies lifecycle hook into
the testcontainer in order to set up remote port forwarding from the
host to the SSHD container so the testcontainer can talk to the host via
the SSHD container
This would be an issue if the testcontainer depends on accessing the
host port on startup ( e.g., a proxy server ) as the forwarding for the
host access isn't set up until all the WiatFor strategies on the
testcontainer have completed.
The fix is to move the forwarding setup to the PreCreates hook on the
testcontainer. Since remote forwarding doesn't establish a connection to
the host port until a connection is made to the remote port, this should
not be an issue even if the host isn't listening yet and ensures the
remote port is available to the testcontainer immediately.
What does this PR do?
Changes the lifecycle hook for the ExposedHostPorts forwarding to happen on PreCreates of the testcontainer instead of PostReadies. Additionally updates the port forwarding tests to ensure the host ports are accessible on startup and that there's no issues even if the host isn't listening until PostCreates.
Why is it important?
Previously ExposedHostPorts would start an SSHD container prior to starting the testcontainer and inject a PostReadies lifecycle hook into the testcontainer in order to set up remote port forwarding from the host to the SSHD container so the testcontainer can talk to the host via the SSHD container
This would be an issue if the testcontainer depends on accessing the host port on startup ( e.g., a proxy server ) as the forwarding for the host access isn't set up until all the WiatFor strategies on the testcontainer have completed.
Related issues
How to test this PR
go test port_forwarding_test.goAdditionally, I've provided a minimal example of my use case where I ran into the issue trying to test my Caddy configuration as an API gateway using testcontainers.