From c5bec9c1fe2e84dbfd6b8d2151de87e36032aace Mon Sep 17 00:00:00 2001 From: Romain Laurent Date: Wed, 2 Oct 2024 14:51:44 +0200 Subject: [PATCH] feat(ryuk): make listen address of exposed port configurable --- internal/config/config.go | 26 +++++++++++++++++++++++++- internal/config/config_test.go | 33 +++++++++++++++++++++++++++++++++ reaper.go | 9 +++++++-- reaper_test.go | 16 ++++++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index a172fa3a16..8aee606407 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,6 +2,7 @@ package config import ( "fmt" + "net" "os" "path/filepath" "strconv" @@ -11,7 +12,10 @@ import ( "github.com/magiconair/properties" ) -const ReaperDefaultImage = "testcontainers/ryuk:0.9.0" +const ( + ReaperDefaultImage = "testcontainers/ryuk:0.9.0" + ReaperDefaultPort = "8080/tcp" +) var ( tcConfig Config @@ -66,6 +70,12 @@ type Config struct { // Environment variable: TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED RyukPrivileged bool `properties:"ryuk.container.privileged,default=false"` + // RyukAddress is the address where the Garbage Collector container is listening. Should be a valid IP address. + // Default make the container listen on 0.0.0.0 . + // + // Environment variable: TESTCONTAINERS_RYUK_ADDRESS + RyukAddress string `properties:"ryuk.container.address,default="` + // RyukReconnectionTimeout is the time to wait before attempting to reconnect to the Garbage Collector container. // // Environment variable: TESTCONTAINERS_RYUK_RECONNECTION_TIMEOUT @@ -126,6 +136,11 @@ func read() Config { config.RyukPrivileged = ryukPrivilegedEnv == "true" } + ryukAddress := os.Getenv("TESTCONTAINERS_RYUK_ADDRESS") + if ryukAddress != "" { + config.RyukAddress = parseIP(ryukAddress) + } + ryukVerboseEnv := os.Getenv("TESTCONTAINERS_RYUK_VERBOSE") if parseBool(ryukVerboseEnv) { config.RyukVerbose = ryukVerboseEnv == "true" @@ -168,3 +183,12 @@ func parseBool(input string) bool { _, err := strconv.ParseBool(input) return err == nil } + +func parseIP(input string) string { + ip := net.ParseIP(input) + if ip == nil { + panic("invalid IP address: " + input) + } + + return ip.String() +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 319deb85b7..6af67a8049 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const ( @@ -24,6 +25,7 @@ func resetTestEnv(t *testing.T) { t.Setenv("TESTCONTAINERS_RYUK_VERBOSE", "") t.Setenv("TESTCONTAINERS_RYUK_RECONNECTION_TIMEOUT", "") t.Setenv("TESTCONTAINERS_RYUK_CONNECTION_TIMEOUT", "") + t.Setenv("TESTCONTAINERS_RYUK_ADDRESS", "") } func TestReadConfig(t *testing.T) { @@ -140,6 +142,15 @@ func TestReadTCConfig(t *testing.T) { assert.Equal(t, expected, config) }) + t.Run("Invalid IP address", func(t *testing.T) { + t.Setenv("TESTCONTAINERS_RYUK_ADDRESS", "invalid") + t.Cleanup(func() { + os.Unsetenv("TESTCONTAINERS_RYUK_ADDRESS") + }) + + require.Panics(t, func() { read() }) + }) + t.Run("HOME contains TC properties file", func(t *testing.T) { defaultRyukConnectionTimeout := 60 * time.Second defaultRyukReconnectionTimeout := 10 * time.Second @@ -480,6 +491,28 @@ func TestReadTCConfig(t *testing.T) { }, defaultConfig, }, + { + "address-properties", + `ryuk.container.address=127.0.0.1`, + map[string]string{}, + Config{ + RyukConnectionTimeout: defaultRyukConnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + RyukAddress: parseIP("127.0.0.1"), + }, + }, + { + "address-env-override", + `ryuk.container.address=127.0.0.1`, + map[string]string{ + "TESTCONTAINERS_RYUK_ADDRESS": "172.17.0.1", + }, + Config{ + RyukConnectionTimeout: defaultRyukConnectionTimeout, + RyukReconnectionTimeout: defaultRyukReconnectionTimeout, + RyukAddress: parseIP("172.17.0.1"), + }, + }, { "With Hub image name prefix set as a property", `hub.image.name.prefix=` + defaultHubPrefix + `/props/`, diff --git a/reaper.go b/reaper.go index c41520b5b7..948f805cc5 100644 --- a/reaper.go +++ b/reaper.go @@ -240,13 +240,18 @@ func newReaper(ctx context.Context, sessionID string, provider ReaperProvider) ( SessionID: sessionID, } - listeningPort := nat.Port("8080/tcp") + listeningPort := nat.Port(config.ReaperDefaultPort) tcConfig := provider.Config().Config + exposedPort := string(listeningPort) + if tcConfig.RyukAddress != "" { + exposedPort = net.JoinHostPort(tcConfig.RyukAddress, "") + ":" + exposedPort + } + req := ContainerRequest{ Image: config.ReaperDefaultImage, - ExposedPorts: []string{string(listeningPort)}, + ExposedPorts: []string{exposedPort}, Labels: core.DefaultLabels(sessionID), Privileged: tcConfig.RyukPrivileged, WaitingFor: wait.ForListeningPort(listeningPort), diff --git a/reaper_test.go b/reaper_test.go index f421c2686d..10b240117a 100644 --- a/reaper_test.go +++ b/reaper_test.go @@ -408,6 +408,22 @@ func Test_NewReaper(t *testing.T) { "TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX": "registry.mycompany.com/mirror", }, }, + { + name: "Reaper with a custom listen address", + req: createContainerRequest(func(req ContainerRequest) ContainerRequest { + req.ExposedPorts = []string{ + "127.0.0.1::8080/tcp", + } + req.Image = config.ReaperDefaultImage + return req + }), + config: TestcontainersConfig{Config: config.Config{ + RyukAddress: "127.0.0.1", + RyukConnectionTimeout: time.Minute, + RyukReconnectionTimeout: 10 * time.Second, + }}, + env: map[string]string{}, + }, } for _, test := range tests {