From 4e9cb66eb079454b3f800c2e1a25cae4013d1d85 Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Mon, 12 Aug 2024 11:20:56 +0100 Subject: [PATCH] feat(wait): skip internal host port check (#2691) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(wait): skip internal host port check Add the ability to skip the internal host port check when performing as HostPortStrategy check. This is useful when a container does bind to the internal port until additional conditions are met. Also improve wait for port function documentation while there. * docs: document skip internal check --------- Co-authored-by: Manuel de la Peña --- docs/features/wait/host_port.md | 21 +++++++++++++++++++ wait/host_port.go | 37 +++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/docs/features/wait/host_port.md b/docs/features/wait/host_port.md index edfad5a188..1b6090b584 100644 --- a/docs/features/wait/host_port.md +++ b/docs/features/wait/host_port.md @@ -6,6 +6,7 @@ The host-port wait strategy will check if the container is listening to a specif - alternatively, wait for the lowest exposed port in the container. - the startup timeout to be used, default is 60 seconds. - the poll interval to be used, default is 100 milliseconds. +- skip the internal check. Variations on the HostPort wait strategy are supported, including: @@ -39,3 +40,23 @@ req := ContainerRequest{ WaitingFor: wait.ForExposedPort(), } ``` + +## Skipping the internal check + +_Testcontainers for Go_ checks if the container is listening to the port internally before returning the control to the caller. For that it uses a shell command to check the port status: + + +[Internal check](../../../wait/host_port.go) inside_block:buildInternalCheckCommand + + +But there are cases where this internal check is not needed, for example when a shell is not available in the container or +when the container doesn't bind the port internally until additional conditions are met. +In this case, the `wait.ForExposedPort.SkipInternalCheck` can be used to skip the internal check. + +```golang +req := ContainerRequest{ + Image: "docker.io/nginx:alpine", + ExposedPorts: []string{"80/tcp", "9080/tcp"}, + WaitingFor: wait.ForExposedPort().SkipInternalCheck(), +} +``` diff --git a/wait/host_port.go b/wait/host_port.go index 9d47a0b39d..b349cc0371 100644 --- a/wait/host_port.go +++ b/wait/host_port.go @@ -28,9 +28,15 @@ type HostPortStrategy struct { // all WaitStrategies should have a startupTimeout to avoid waiting infinitely timeout *time.Duration PollInterval time.Duration + + // skipInternalCheck is a flag to skip the internal check, which is useful when + // a shell is not available in the container or when the container doesn't bind + // the port internally until additional conditions are met. + skipInternalCheck bool } -// NewHostPortStrategy constructs a default host port strategy +// NewHostPortStrategy constructs a default host port strategy that waits for the given +// port to be exposed. The default startup timeout is 60 seconds. func NewHostPortStrategy(port nat.Port) *HostPortStrategy { return &HostPortStrategy{ Port: port, @@ -42,18 +48,28 @@ func NewHostPortStrategy(port nat.Port) *HostPortStrategy { // since go has neither covariance nor generics, the return type must be the type of the concrete implementation // this is true for all properties, even the "shared" ones like startupTimeout -// ForListeningPort is a helper similar to those in Wait.java -// https://github.com/testcontainers/testcontainers-java/blob/1d85a3834bd937f80aad3a4cec249c027f31aeb4/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java +// ForListeningPort returns a host port strategy that waits for the given port +// to be exposed and bound internally the container. +// Alias for `NewHostPortStrategy(port)`. func ForListeningPort(port nat.Port) *HostPortStrategy { return NewHostPortStrategy(port) } -// ForExposedPort constructs an exposed port strategy. Alias for `NewHostPortStrategy("")`. -// This strategy waits for the first port exposed in the Docker container. +// ForExposedPort returns a host port strategy that waits for the first port +// to be exposed and bound internally the container. func ForExposedPort() *HostPortStrategy { return NewHostPortStrategy("") } +// SkipInternalCheck changes the host port strategy to skip the internal check, +// which is useful when a shell is not available in the container or when the +// container doesn't bind the port internally until additional conditions are met. +func (hp *HostPortStrategy) SkipInternalCheck() *HostPortStrategy { + hp.skipInternalCheck = true + + return hp +} + // WithStartupTimeout can be used to change the default startup timeout func (hp *HostPortStrategy) WithStartupTimeout(startupTimeout time.Duration) *HostPortStrategy { hp.timeout = &startupTimeout @@ -130,6 +146,10 @@ func (hp *HostPortStrategy) WaitUntilReady(ctx context.Context, target StrategyT return err } + if hp.skipInternalCheck { + return nil + } + err = internalCheck(ctx, internalPort, target) if err != nil && errors.Is(errShellNotExecutable, err) { log.Println("Shell not executable in container, only external port check will be performed") @@ -164,12 +184,11 @@ func externalCheck(ctx context.Context, ipAddress string, port nat.Port, target } } return err - } else { - _ = conn.Close() - break } + + conn.Close() + return nil } - return nil } func internalCheck(ctx context.Context, internalPort nat.Port, target StrategyTarget) error {