mirror of
https://github.com/stan220/testcontainers-php.git
synced 2026-09-08 17:29:29 +00:00
Use inspect Model instead of plain Response after fix in beluga-php/docker-php. Update resolving host logic.
This commit is contained in:
@@ -0,0 +1,138 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Utils;
|
||||||
|
|
||||||
|
use Docker\API\Model\Network;
|
||||||
|
use Docker\Docker;
|
||||||
|
use RuntimeException;
|
||||||
|
use Testcontainers\Container\GenericContainer;
|
||||||
|
use Testcontainers\ContainerClient\DockerContainerClient;
|
||||||
|
|
||||||
|
class HostResolver
|
||||||
|
{
|
||||||
|
public function __construct(protected ?Docker $dockerClient = null)
|
||||||
|
{
|
||||||
|
$this->dockerClient = $dockerClient ?? DockerContainerClient::getDockerClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the host address for connecting to a container.
|
||||||
|
*
|
||||||
|
* The resolution process is as follows:
|
||||||
|
* 1. If user overrides are allowed and TESTCONTAINERS_HOST_OVERRIDE is set, its value is returned.
|
||||||
|
* 2. Otherwise, the DOCKER_HOST environment variable is parsed.
|
||||||
|
* - If the scheme is one of http, https, or tcp, the hostname is used.
|
||||||
|
* - If the scheme is unix or npipe and the process is running in a container, the network gateway
|
||||||
|
* is determined by inspecting the relevant Docker network or running a temporary container.
|
||||||
|
* 3. If no other value can be determined, "localhost" is returned.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @throws RuntimeException If the DOCKER_HOST scheme is unsupported.
|
||||||
|
*/
|
||||||
|
public function resolveHost(): string
|
||||||
|
{
|
||||||
|
if ($this->allowUserOverrides() && ($override = getenv('TESTCONTAINERS_HOST_OVERRIDE')) !== false) {
|
||||||
|
return $override;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get DOCKER_HOST URI, defaulting to a TCP endpoint if not set.
|
||||||
|
$dockerHostUri = getenv('DOCKER_HOST') ?: 'tcp://127.0.0.1:2375';
|
||||||
|
$parts = parse_url($dockerHostUri);
|
||||||
|
if ($parts === false || !isset($parts['scheme'])) {
|
||||||
|
return 'localhost';
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheme = $parts['scheme'];
|
||||||
|
|
||||||
|
switch ($scheme) {
|
||||||
|
case 'http':
|
||||||
|
case 'https':
|
||||||
|
case 'tcp':
|
||||||
|
return $parts['host'] ?? 'localhost';
|
||||||
|
|
||||||
|
case 'unix':
|
||||||
|
case 'npipe':
|
||||||
|
if ($this->isInContainer()) {
|
||||||
|
// If using podman, choose "podman" network; otherwise, use "bridge"
|
||||||
|
$networkName = (str_contains($dockerHostUri, 'podman.sock')) ? 'podman' : 'bridge';
|
||||||
|
if ($gateway = $this->findGateway($networkName)) {
|
||||||
|
return $gateway;
|
||||||
|
}
|
||||||
|
if ($defaultGateway = $this->findDefaultGateway()) {
|
||||||
|
return $defaultGateway;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'localhost';
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Unsupported Docker host scheme: {$scheme}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function allowUserOverrides(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the code is running inside a container.
|
||||||
|
*/
|
||||||
|
protected function isInContainer(): bool
|
||||||
|
{
|
||||||
|
return file_exists('/.dockerenv');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspects the given network and returns its gateway IP address if found.
|
||||||
|
*
|
||||||
|
* @param string $networkName
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
protected function findGateway(string $networkName): ?string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
/** @var Network|null $networkInspect */
|
||||||
|
$networkInspect = $this->dockerClient?->networkInspect($networkName);
|
||||||
|
$ipamConfig = $networkInspect?->getIPAM()?->getConfig();
|
||||||
|
if ($ipamConfig !== null) {
|
||||||
|
foreach ($ipamConfig as $config) {
|
||||||
|
if ($config->getGateway() !== null) {
|
||||||
|
return $config->getGateway();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a temporary container to determine the default gateway.
|
||||||
|
*/
|
||||||
|
protected function findDefaultGateway(): ?string
|
||||||
|
{
|
||||||
|
$tmpContainer = null;
|
||||||
|
try {
|
||||||
|
// Create a temporary container using a lightweight Alpine image.
|
||||||
|
$tmpContainer = (new GenericContainer('alpine:3.14'))
|
||||||
|
->withCommand(['tail', '-f', '/dev/null'])
|
||||||
|
->start();
|
||||||
|
$result = $tmpContainer->exec(['sh', '-c', "ip route | awk '/default/ { print $3 }'"]);
|
||||||
|
$tmpContainer->stop();
|
||||||
|
return $result;
|
||||||
|
} catch (\Throwable) {
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if ($tmpContainer !== null) {
|
||||||
|
try {
|
||||||
|
$tmpContainer->stop();
|
||||||
|
} catch (\Throwable) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user