Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,42 @@ jobs:
run: composer run phpstan

phpunit:
runs-on: ubuntu-latest
name: Integration Tests
runs-on: ${{ matrix.os }}
name: Integration Tests (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
include:
- os: ubuntu-latest
docker_host: unix:///var/run/docker.sock
- os: windows-latest
docker_host: tcp://localhost:2375

steps:
- uses: actions/checkout@v4


- name: Configure Docker to use TCP (Windows)
if: matrix.os == 'windows-latest'
run: |
# Configure Docker daemon to listen on TCP
$dockerConfig = @{ hosts = @("tcp://0.0.0.0:2375", "npipe:////./pipe/docker_engine") }
$dockerConfig | ConvertTo-Json | Set-Content -Path "C:\ProgramData\docker\config\daemon.json"
Restart-Service docker
# Wait for Docker to be ready
Start-Sleep -Seconds 5
shell: pwsh

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: redis, pgsql, mongodb-mongodb/mongo-php-driver@1.15.0
extensions: curl, pdo, pdo_mysql, pdo_pgsql, redis

- name: Install dependencies
run: composer install --prefer-dist --no-progress

- name: Run test suite
run: composer run integration
env:
DOCKER_HOST: ${{ matrix.docker_host }}
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
],
"require": {
"ext-curl": "*",
"php": ">= 8.1",
"beluga-php/docker-php": "^1.45"
"php": ">= 8.1"
},
"require-dev": {
"ext-pdo": "*",
Expand Down
41 changes: 23 additions & 18 deletions src/Container/GenericContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@

namespace Testcontainers\Container;

use Docker\API\Exception\ContainerCreateNotFoundException;
use Docker\API\Model\ContainerCreateResponse;
use Docker\API\Model\ContainersCreatePostBody;
use Docker\API\Model\EndpointSettings;
use Docker\API\Model\HealthConfig;
use Docker\API\Model\HostConfig;
use Docker\API\Model\Mount;
use Docker\API\Model\NetworkingConfig;
use Docker\API\Model\PortBinding;
use Docker\Docker;
use Docker\Stream\CreateImageStream;
use Testcontainers\Docker\DockerClient;
use Testcontainers\Docker\Exception\ContainerCreateNotFoundException;
use Testcontainers\Docker\Model\ContainerCreateResponse;
use Testcontainers\Docker\Model\ContainersCreatePostBody;
use Testcontainers\Docker\Model\EndpointSettings;
use Testcontainers\Docker\Model\HealthConfig;
use Testcontainers\Docker\Model\HostConfig;
use Testcontainers\Docker\Model\Mount;
use Testcontainers\Docker\Model\NetworkingConfig;
use Testcontainers\Docker\Model\PortBinding;
use Testcontainers\Docker\Stream\CreateImageStream;
use InvalidArgumentException;
use RuntimeException;
use Testcontainers\ContainerClient\DockerContainerClient;
Expand All @@ -28,7 +28,7 @@

class GenericContainer implements TestContainer
{
protected Docker $dockerClient;
protected DockerClient $dockerClient;

protected string $image;

Expand Down Expand Up @@ -370,10 +370,7 @@ protected function copyToContainer(
$queryParams['copyUIDGID'] = 'true';
}

/**
* TODO: should be improved. Currently without using dummy $result or FETCH_RESPONSE, the request is failing.
* Probably an issue with the beluga-php/docker-php client library.
* */
// Ensure the request body is fully sent and response is consumed.
$result = $this->dockerClient->putContainerArchive(
$this->id,
$handle,
Expand Down Expand Up @@ -402,6 +399,14 @@ protected function createContainerConfig(): ContainersCreatePostBody
$hostConfig = $this->createHostConfig();
$containerCreatePostBody->setHostConfig($hostConfig);

if ($this->exposedPorts !== []) {
$exposed = [];
foreach ($this->exposedPorts as $port) {
$exposed[$port] = new \stdClass();
}
$containerCreatePostBody->setExposedPorts($exposed);
}

if ($this->entryPoint !== null) {
$containerCreatePostBody->setEntrypoint([$this->entryPoint]);
}
Expand Down Expand Up @@ -475,7 +480,7 @@ protected function pullImage(): void
{
[$fromImage, $tag] = explode(':', $this->image) + [1 => 'latest'];

// Build headers for the request
// Build headers for the request (curl-style list format)
$headers = [];

// Try to get authentication for the registry
Expand All @@ -488,7 +493,7 @@ protected function pullImage(): void
'username' => $credentials['username'],
'password' => $credentials['password'],
];
$headers['X-Registry-Auth'] = base64_encode(json_encode($authData, JSON_THROW_ON_ERROR));
$headers[] = 'X-Registry-Auth: ' . base64_encode(json_encode($authData, JSON_THROW_ON_ERROR));
}

/** @var CreateImageStream $imageCreateResponse */
Expand Down
30 changes: 15 additions & 15 deletions src/Container/StartedGenericContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,25 @@

namespace Testcontainers\Container;

use Docker\API\Client;
use Docker\API\Model\ContainersIdExecPostBody;
use Docker\API\Model\ContainersIdJsonGetResponse200;
use Docker\API\Model\EndpointSettings;
use Docker\API\Model\IdResponse;
use Docker\API\Model\PortBinding;
use Docker\API\Runtime\Client\Client as DockerRuntimeClient;
use Docker\Docker;
use Testcontainers\Docker\DockerClient;
use Testcontainers\Docker\Model\ContainersIdExecPostBody;
use Testcontainers\Docker\Model\ContainersIdJsonGetResponse200;
use Testcontainers\Docker\Model\EndpointSettings;
use Testcontainers\Docker\Model\IdResponse;
use Testcontainers\Docker\Model\PortBinding;
use RuntimeException;
use Testcontainers\ContainerClient\DockerContainerClient;
use Testcontainers\Utils\HostResolver;

class StartedGenericContainer implements StartedTestContainer
{
protected Docker $dockerClient;
protected DockerClient $dockerClient;

protected ?ContainersIdJsonGetResponse200 $inspectResponse = null;

protected ?string $lastExecId = null;

public function __construct(protected readonly string $id, ?Docker $dockerClient = null)
public function __construct(protected readonly string $id, ?DockerClient $dockerClient = null)
{
$this->dockerClient = $dockerClient ?? DockerContainerClient::getDockerClient();
}
Expand All @@ -39,7 +37,7 @@ public function getLastExecId(): ?string
return $this->lastExecId;
}

public function getClient(): Docker
public function getClient(): DockerClient
{
return $this->dockerClient;
}
Expand All @@ -65,7 +63,7 @@ public function exec(array $command): string
$this->lastExecId = $exec->getId();

$contents = $this->dockerClient
->execStart($this->lastExecId, null, Client::FETCH_RESPONSE)
->execStart($this->lastExecId, null, DockerClient::FETCH_RESPONSE)
?->getBody()
->getContents() ?? '';

Expand Down Expand Up @@ -93,7 +91,7 @@ public function logs(): string
->containerLogs(
$this->id,
['stdout' => true, 'stderr' => true],
DockerRuntimeClient::FETCH_RESPONSE
DockerClient::FETCH_RESPONSE
)
?->getBody()
->getContents() ?? '';
Expand Down Expand Up @@ -206,10 +204,12 @@ public function getBoundPorts(): iterable
* For some reason, in the latest Docker releases, at this moment, the container might not be fully started.
* This can lead to issues when trying to retrieve the ports.
* TODO: find a better strategy to ensure the container is fully started or run in a loop until it is ready.
* For the loop $this->inspect() shouldn't be cached.
*/
usleep(300 * 1000);
$ports = $this->inspect()?->getNetworkSettings()?->getPorts();
/** @var ContainersIdJsonGetResponse200 | null $inspectResponse */
$inspectResponse = $this->dockerClient->containerInspect($this->id);
$this->inspectResponse = $inspectResponse;
$ports = $inspectResponse?->getNetworkSettings()?->getPorts();

if ($ports === null) {
throw new RuntimeException('Failed to get ports from container');
Expand Down
6 changes: 3 additions & 3 deletions src/Container/StartedTestContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

namespace Testcontainers\Container;

use Docker\API\Model\PortBinding;
use Docker\Docker;
use Testcontainers\Docker\DockerClient;
use Testcontainers\Docker\Model\PortBinding;

interface StartedTestContainer
{
Expand All @@ -19,7 +19,7 @@ public function exec(array $command): string;
*/
public function getBoundPorts(): iterable;

public function getClient(): Docker;
public function getClient(): DockerClient;

public function getFirstMappedPort(): int;

Expand Down
2 changes: 1 addition & 1 deletion src/ContainerClient/DockerContainerClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Testcontainers\ContainerClient;

use Docker\Docker as DockerClient;
use Testcontainers\Docker\DockerClient;

class DockerContainerClient
{
Expand Down
23 changes: 23 additions & 0 deletions src/Docker/Client/ClientInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Testcontainers\Docker\Client;

use Testcontainers\Docker\DockerResponse;

interface ClientInterface
{
/**
* @param array<string, mixed> $query
* @param list<string> $headers
*/
public function request(string $method, string $path, array $query = [], ?string $body = null, array $headers = []): DockerResponse;

/**
* @param resource $handle
* @param array<string, mixed> $query
* @param list<string> $headers
*/
public function requestStream(string $method, string $path, $handle, array $query = [], array $headers = []): DockerResponse;
}
Loading
Loading