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

setup ExposeHostPorts forwards on container start

Open hathvi opened this issue 1 year ago • 4 comments

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))
}

hathvi avatar Oct 05 '24 17:10 hathvi

Deploy Preview for testcontainers-go ready!

Name Link
Latest commit ad3f69803db981288d4501e8de9fd258cb4690a4
Latest deploy log https://app.netlify.com/sites/testcontainers-go/deploys/67017de89ef93400086b7076
Deploy Preview https://deploy-preview-2815--testcontainers-go.netlify.app
Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

netlify[bot] avatar Oct 05 '24 17:10 netlify[bot]

Thanks for the PR, I've replied the issue, as it seems this is possible without using SSHD port forwarding, so wonder if that's the better way?

stevenh avatar Oct 07 '24 19:10 stevenh

Thanks for the PR. As HostAccessPorts is part of the API, it is really expected that this work also at startup time, not just when doing Exec later on. I just lost half a day trying to figure out why this wasn't working and to end up here. Looking forward to see it merged.

cedric-appdirect avatar Oct 18 '24 18:10 cedric-appdirect

Thanks for the PR. As HostAccessPorts is part of the API, it is really expected that this work also at startup time, not just when doing Exec later on. I just lost half a day trying to figure out why this wasn't working and to end up here. Looking forward to see it merged.

Thanks for the feedback, sounds like it should be clearly documented or fixed so it works too.

stevenh avatar Oct 21 '24 10:10 stevenh

Hi @hathvi I'm reviewing this PR, and found there are conflicts that must be resolved. I wanted to do it myself locally, but as this PR was sent from your main branch, I cannot push my changes to it. Would you mind resolving them and pushing them again please?

I'd like to include this fix into the next release if possible.

Thanks

mdelapenya avatar Apr 22 '25 11:04 mdelapenya

@hathvi I'm closing this one in favour of https://github.com/testcontainers/testcontainers-go/pull/3200. I've moved there the PR description and all your commits, for attribution. I'll also update the release notes to make you appear there as author of the original PR. Please read https://github.com/testcontainers/testcontainers-go/pull/2815#issuecomment-2821087290 about submitting PRs from the main branch 🙏

If you have any concern or question, let's follow-up in #3200

mdelapenya avatar Jun 03 '25 12:06 mdelapenya