testcontainers-go icon indicating copy to clipboard operation
testcontainers-go copied to clipboard

chore: setup ExposeHostPorts forward on container start hook

Open mdelapenya opened this issue 4 months ago • 1 comments

Fixes testcontainers/testcontainers-go#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

  • Fixes #2811

How to test this PR

go test port_forwarding_test.go

Additionally, 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.

package caddy_test

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/wait"
)

const caddyFileContent = `
listen :80

reverse_proxy /api/* {
	to {$API_SERVER}

	health_uri /health
	health_status 200
	health_interval 10s
}
`

func TestCaddyfile(t *testing.T) {
	ctx := context.Background()

	apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, World!")
	}))
	apiServerPort := apiServer.Listener.Addr().(*net.TCPAddr).Port

	caddyContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
		ContainerRequest: testcontainers.ContainerRequest{
			Image:        "caddy:2.8.4",
			ExposedPorts: []string{"80/tcp"},
			WaitingFor:   wait.ForLog("server running"),
			Env: map[string]string{
				"API_SERVER": fmt.Sprintf("http://%s:%d", testcontainers.HostInternal, apiServerPort),
			},
			Files: []testcontainers.ContainerFile{
				{
					Reader:            bytes.NewReader([]byte(caddyFileContent)),
					ContainerFilePath: "/etc/caddy/Caddyfile",
				},
			},
			HostAccessPorts: []int{apiServerPort},
		},
		Started: true,
	})
	require.NoError(t, err)
	defer caddyContainer.Terminate(ctx)

	caddyURL, err := caddyContainer.PortEndpoint(ctx, "80/tcp", "http")
	require.NoError(t, err)

	resp, err := http.Get(caddyURL + "/api/test")
	require.NoError(t, err)
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	assert.Equal(t, http.StatusOK, resp.StatusCode)
	assert.Equal(t, "Hello, World!", string(body))

	lr, err := caddyContainer.Logs(ctx)
	assert.NoError(t, err)
	lb, err := io.ReadAll(lr)
	assert.NoError(t, err)
	fmt.Printf("== Caddy Logs ==\n%s================\n\n", string(lb))
}

mdelapenya avatar Jun 03 '25 12:06 mdelapenya