From f8a09cb5871e931b6523e8c5c1e41589c683ed2b Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Fri, 12 Jul 2024 20:54:12 +0100 Subject: [PATCH] fix: endpoint and http wait use of wrong port Ensure Container.Endpoint returns the lowest numbered aka first port there is more than one exposed. Ensure that wait.ForHTTP uses the lowest numbered aka first port when there is more than one exposed. Prior to this a random port would be returned leading to unpredictable results. Fixed: #2640 --- docker.go | 17 ++++++++--------- wait/host_port.go | 10 +++------- wait/http.go | 47 ++++++++++++++++++++++++++--------------------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/docker.go b/docker.go index 4a641b87085..7688a77d7df 100644 --- a/docker.go +++ b/docker.go @@ -113,7 +113,7 @@ func (c *DockerContainer) IsRunning() bool { return c.isRunning } -// Endpoint gets proto://host:port string for the first exposed port +// Endpoint gets proto://host:port string for the lowest numbered exposed port // Will returns just host:port if proto is "" func (c *DockerContainer) Endpoint(ctx context.Context, proto string) (string, error) { inspect, err := c.Inspect(ctx) @@ -121,16 +121,15 @@ func (c *DockerContainer) Endpoint(ctx context.Context, proto string) (string, e return "", err } - ports := inspect.NetworkSettings.Ports - - // get first port - var firstPort nat.Port - for p := range ports { - firstPort = p - break + // Get lowest numbered bound port. + var lowestPort nat.Port + for port := range inspect.NetworkSettings.Ports { + if lowestPort == "" || port.Int() < lowestPort.Int() { + lowestPort = port + } } - return c.PortEndpoint(ctx, firstPort, proto) + return c.PortEndpoint(ctx, lowestPort, proto) } // PortEndpoint gets proto://host:port string for the given exposed port diff --git a/wait/host_port.go b/wait/host_port.go index fc95492a0da..9d47a0b39d9 100644 --- a/wait/host_port.go +++ b/wait/host_port.go @@ -89,18 +89,14 @@ func (hp *HostPortStrategy) WaitUntilReady(ctx context.Context, target StrategyT internalPort := hp.Port if internalPort == "" { - var ports nat.PortMap inspect, err := target.Inspect(ctx) if err != nil { return err } - ports = inspect.NetworkSettings.Ports - - if len(ports) > 0 { - for p := range ports { - internalPort = p - break + for port := range inspect.NetworkSettings.Ports { + if internalPort == "" || port.Int() < internalPort.Int() { + internalPort = port } } } diff --git a/wait/http.go b/wait/http.go index c93cec59c83..d05ae0d39ac 100644 --- a/wait/http.go +++ b/wait/http.go @@ -76,6 +76,8 @@ func (ws *HTTPStrategy) WithStartupTimeout(timeout time.Duration) *HTTPStrategy return ws } +// WithPort set the port to wait for. +// Default is the lowest numbered port. func (ws *HTTPStrategy) WithPort(port nat.Port) *HTTPStrategy { ws.Port = port return ws @@ -173,38 +175,41 @@ func (ws *HTTPStrategy) WaitUntilReady(ctx context.Context, target StrategyTarge var mappedPort nat.Port if ws.Port == "" { - var err error - var ports nat.PortMap // we wait one polling interval before we grab the ports otherwise they might not be bound yet on startup - for err != nil || ports == nil { - select { - case <-ctx.Done(): - return fmt.Errorf("%w: %w", ctx.Err(), err) - case <-time.After(ws.PollInterval): - if err := checkTarget(ctx, target); err != nil { - return err - } + select { + case <-ctx.Done(): + return fmt.Errorf("%w: %w", ctx.Err(), err) + case <-time.After(ws.PollInterval): + } - inspect, err := target.Inspect(ctx) - if err != nil { - return err - } + if err := checkTarget(ctx, target); err != nil { + return err + } - ports = inspect.NetworkSettings.Ports - } + inspect, err := target.Inspect(ctx) + if err != nil { + return err } - for k, bindings := range ports { - if len(bindings) == 0 || k.Proto() != "tcp" { + // Find the lowest numbered exposed tcp port. + var lowestPort nat.Port + var hostPort string + for port, bindings := range inspect.NetworkSettings.Ports { + if len(bindings) == 0 || port.Proto() != "tcp" { continue } - mappedPort, _ = nat.NewPort(k.Proto(), bindings[0].HostPort) - break + + if lowestPort == "" || port.Int() < mappedPort.Int() { + lowestPort = port + hostPort = bindings[0].HostPort + } } - if mappedPort == "" { + if lowestPort == "" { return errors.New("No exposed tcp ports or mapped ports - cannot wait for status") } + + mappedPort, _ = nat.NewPort(lowestPort.Proto(), hostPort) } else { mappedPort, err = target.MappedPort(ctx, ws.Port)