From 6c3c74c81721f2e9c0cc89b758efd72dc875f12a Mon Sep 17 00:00:00 2001 From: Nikola Petkanski Date: Tue, 16 Jul 2024 01:19:02 +0300 Subject: [PATCH] Support for waiting on tcp port to open --- src/Container/Container.php | 5 +++ src/Wait/WaitForTcpPortOpen.php | 55 ++++++++++++++++++++++++++ tests/Integration/WaitStrategyTest.php | 38 ++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 src/Wait/WaitForTcpPortOpen.php diff --git a/src/Container/Container.php b/src/Container/Container.php index 1317383..46776c1 100644 --- a/src/Container/Container.php +++ b/src/Container/Container.php @@ -56,6 +56,11 @@ class Container return new Container($image); } + public function getId(): string + { + return $this->id; + } + public function withEntryPoint(string $entryPoint): self { $this->entryPoint = $entryPoint; diff --git a/src/Wait/WaitForTcpPortOpen.php b/src/Wait/WaitForTcpPortOpen.php new file mode 100644 index 0000000..14946ba --- /dev/null +++ b/src/Wait/WaitForTcpPortOpen.php @@ -0,0 +1,55 @@ + + */ +final class WaitForTcpPortOpen implements WaitInterface +{ + public function __construct(private readonly int $port) + { + } + + public static function make(int $port): self + { + return new self($port); + } + + /** + * @throws JsonException + */ + public function wait(string $id): void + { + if (@fsockopen($this->findContainerAddress($id), $this->port) === false) { + throw new ContainerNotReadyException($id, new RuntimeException('Unable to connect to container TCP port')); + } + } + + /** + * @throws JsonException + */ + private function findContainerAddress(string $id): string + { + $process = new Process(['docker', 'inspect', $id]); + $process->mustRun(); + + /** @var ContainerInspect $data */ + $data = json_decode($process->getOutput(), true, 512, JSON_THROW_ON_ERROR); + + $containerAddress = $data[0]['NetworkSettings']['IPAddress'] ?? null; + + if (! is_string($containerAddress)) { + throw new ContainerNotReadyException($id, new RuntimeException('Unable to find container IP address')); + } + + return $containerAddress; + } +} diff --git a/tests/Integration/WaitStrategyTest.php b/tests/Integration/WaitStrategyTest.php index 2424196..455e07b 100644 --- a/tests/Integration/WaitStrategyTest.php +++ b/tests/Integration/WaitStrategyTest.php @@ -9,10 +9,12 @@ use Predis\Client; use Predis\Connection\ConnectionException; use Symfony\Component\Process\Process; use Testcontainer\Container\Container; +use Testcontainer\Exception\ContainerNotReadyException; use Testcontainer\Wait\WaitForExec; use Testcontainer\Wait\WaitForHealthCheck; use Testcontainer\Wait\WaitForHttp; use Testcontainer\Wait\WaitForLog; +use Testcontainer\Wait\WaitForTcpPortOpen; class WaitStrategyTest extends TestCase { @@ -90,6 +92,42 @@ class WaitStrategyTest extends TestCase $this->assertNotEmpty($response); } + /** + * @dataProvider provideWaitForTcpPortOpen + */ + public function testWaitForTcpPortOpen(bool $canConnect): void + { + $container = Container::make('nginx:alpine'); + + if ($canConnect) { + $container->withWait(WaitForTcpPortOpen::make(80)); + } + + $container->run(); + + if ($canConnect) { + static::assertIsResource(fsockopen($container->getAddress(), 80), 'Failed to connect to container'); + return; + } + + $containerId = $container->getId(); + + $this->expectExceptionObject(new ContainerNotReadyException($containerId)); + + (new WaitForTcpPortOpen(8080))->wait($containerId); + } + + /** + * @return array> + */ + public function provideWaitForTcpPortOpen(): array + { + return [ + 'Can connect to container' => [true], + 'Cannot connect to container' => [false], + ]; + } + public function testWaitForHealthCheck(): void { $container = Container::make('nginx')