Skip to content
Merged
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
11 changes: 11 additions & 0 deletions src/Internal/CommandHandler/ContainerCommandHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
use TinyBlocks\DockerContainer\Internal\Commands\Command;
use TinyBlocks\DockerContainer\Internal\Commands\DockerCopy;
use TinyBlocks\DockerContainer\Internal\Commands\DockerList;
use TinyBlocks\DockerContainer\Internal\Commands\DockerNetworkConnect;
use TinyBlocks\DockerContainer\Internal\Commands\DockerNetworkCreate;
use TinyBlocks\DockerContainer\Internal\Commands\DockerRun;
use TinyBlocks\DockerContainer\Internal\Containers\ContainerLookup;
use TinyBlocks\DockerContainer\Internal\Containers\Definitions\ContainerDefinition;
use TinyBlocks\DockerContainer\Internal\Containers\Definitions\CopyInstruction;
use TinyBlocks\DockerContainer\Internal\Containers\HostEnvironment;
use TinyBlocks\DockerContainer\Internal\Containers\Models\ContainerId;
use TinyBlocks\DockerContainer\Internal\Containers\ShutdownHook;
use TinyBlocks\DockerContainer\Internal\Exceptions\DockerCommandExecutionFailed;
Expand Down Expand Up @@ -53,6 +55,15 @@ public function run(DockerRun $dockerRun): ContainerStarted
{
if (!is_null($dockerRun->definition->network)) {
$this->client->execute(command: DockerNetworkCreate::from(network: $dockerRun->definition->network));

if (HostEnvironment::isInsideDocker()) {
$this->client->execute(
command: DockerNetworkConnect::from(
network: $dockerRun->definition->network,
container: HostEnvironment::containerHostname()
Comment thread
gustavofreze marked this conversation as resolved.
)
);
}
}

$executionCompleted = $this->client->execute(command: $dockerRun);
Expand Down
22 changes: 22 additions & 0 deletions src/Internal/Commands/DockerNetworkConnect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace TinyBlocks\DockerContainer\Internal\Commands;

final readonly class DockerNetworkConnect implements Command
{
private function __construct(private string $network, private string $container)
{
}

public static function from(string $network, string $container): DockerNetworkConnect
{
return new DockerNetworkConnect(network: $network, container: $container);
}

public function toCommandLine(): string
{
return sprintf('docker network connect %s %s 2>/dev/null || true', $this->network, $this->container);
Comment thread
gustavofreze marked this conversation as resolved.
}
}
18 changes: 18 additions & 0 deletions src/Internal/Containers/HostEnvironment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace TinyBlocks\DockerContainer\Internal\Containers;

final readonly class HostEnvironment
{
public static function isInsideDocker(): bool
{
return file_exists('/.dockerenv');
}

public static function containerHostname(): string
{
return (string) gethostname();
Comment thread
gustavofreze marked this conversation as resolved.
}
}
39 changes: 38 additions & 1 deletion tests/Unit/GenericDockerContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Test\Unit;

use InvalidArgumentException;
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
use PHPUnit\Framework\TestCase;
use Test\Unit\Mocks\ClientMock;
use Test\Unit\Mocks\InspectResponseFixture;
Expand Down Expand Up @@ -924,7 +925,7 @@ public function testRunCommandLineIncludesNetwork(): void
);

/** @And the docker run command should contain the network argument */
$runCommand = $this->client->getExecutedCommandLines()[1];
$runCommand = $this->client->getExecutedCommandLines()[2];
self::assertStringContainsString(needle: '--network=my-network', haystack: $runCommand);
}

Expand Down Expand Up @@ -1248,4 +1249,40 @@ public function testRunIfNotExistsSkipsReaperCreationWhenReaperAlreadyExists():
);
}
}

#[RunInSeparateProcess]
public function testRunContainerWithNetworkWhenOutsideDockerSkipsHostConnection(): void
{
require_once __DIR__ . '/Internal/Containers/Overrides/file_exists_outside_docker.php';

/** @Given a container configured with a network, running outside a Docker environment */
$client = new ClientMock();
$container = TestableGenericDockerContainer::createWith(
image: 'alpine:latest',
name: 'outside-docker',
client: $client
)->withNetwork(name: 'my-network');

/** @And the Docker daemon returns valid responses */
$client->withDockerRunResponse(output: InspectResponseFixture::containerId());
$client->withDockerInspectResponse(
inspectResult: InspectResponseFixture::build(hostname: 'outside-docker')
);

/** @When the container is started */
$started = $container->run();

/** @Then the container should be running */
self::assertSame(expected: 'outside-docker', actual: $started->getName());

/** @And no network connect command should have been executed for the host */
$commandLines = $client->getExecutedCommandLines();

foreach ($commandLines as $commandLine) {
self::assertStringNotContainsString(
needle: 'docker network connect',
haystack: $commandLine
);
}
}
}
12 changes: 8 additions & 4 deletions tests/Unit/Mocks/ClientMock.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
use TinyBlocks\DockerContainer\Internal\Commands\DockerExecute;
use TinyBlocks\DockerContainer\Internal\Commands\DockerInspect;
use TinyBlocks\DockerContainer\Internal\Commands\DockerList;
use TinyBlocks\DockerContainer\Internal\Commands\DockerNetworkConnect;
use TinyBlocks\DockerContainer\Internal\Commands\DockerNetworkCreate;
use TinyBlocks\DockerContainer\Internal\Commands\DockerPull;
use TinyBlocks\DockerContainer\Internal\Commands\DockerRun;
use TinyBlocks\DockerContainer\Internal\Commands\DockerStop;
Expand Down Expand Up @@ -102,10 +104,12 @@ public function execute(Command $command): ExecutionCompleted
json_encode([($inspectData = array_shift($this->inspectResponses))]),
!empty($inspectData)
],
$command instanceof DockerCopy => ['', true],
$command instanceof DockerPull => ['', true],
$command instanceof DockerStop => array_shift($this->stopResponses) ?? ['', true],
default => ['', false]
$command instanceof DockerCopy => ['', true],
$command instanceof DockerPull => ['', true],
$command instanceof DockerStop => array_shift($this->stopResponses) ?? ['', true],
$command instanceof DockerNetworkCreate => ['', true],
$command instanceof DockerNetworkConnect => ['', true],
default => ['', false]
};

return new ExecutionCompletedMock(output: (string)$output, successful: $isSuccessful);
Expand Down
17 changes: 2 additions & 15 deletions tests/Unit/MySQLDockerContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use TinyBlocks\DockerContainer\Internal\Containers\ShutdownHook;
use TinyBlocks\DockerContainer\Internal\Exceptions\ContainerWaitTimeout;
use TinyBlocks\DockerContainer\Internal\Exceptions\DockerCommandExecutionFailed;
use TinyBlocks\DockerContainer\MySQLDockerContainer;
use TinyBlocks\DockerContainer\Waits\Conditions\ContainerReady;
use TinyBlocks\DockerContainer\Waits\ContainerWaitForDependency;

Expand Down Expand Up @@ -105,7 +104,7 @@ public function testRunMySQLContainerSuccessfully(): void

/** @And the docker run command should reflect delegated configuration */
$commandLines = $this->client->getExecutedCommandLines();
$runCommand = $commandLines[1];
$runCommand = $commandLines[2];

self::assertStringNotContainsString(needle: '--rm', haystack: $runCommand);
self::assertStringContainsString(needle: '--volume /var/lib/mysql:/var/lib/mysql', haystack: $runCommand);
Expand All @@ -116,7 +115,7 @@ public function testRunMySQLContainerSuccessfully(): void
self::assertStringContainsString(needle: "MYSQL_ROOT_PASSWORD='root'", haystack: $runCommand);

/** @And the database setup should include CREATE DATABASE, GRANT, and FLUSH */
$setupCommand = $commandLines[4];
$setupCommand = $commandLines[5];

self::assertStringContainsString(needle: 'CREATE DATABASE IF NOT EXISTS test_adm', haystack: $setupCommand);
self::assertStringContainsString(needle: 'GRANT ALL PRIVILEGES', haystack: $setupCommand);
Expand Down Expand Up @@ -822,18 +821,6 @@ public function testRunMySQLContainerWithPullImage(): void
self::assertTrue($hasPullCommand);
}

public function testFromCreatesMySQLContainerInstance(): void
{
/** @Given a valid MySQL image name */
$image = 'mysql:8.1';

/** @When creating a MySQL container from the image */
$container = MySQLDockerContainer::from(image: $image, name: 'from-mysql');

/** @Then the container should be an instance of MySQLDockerContainer */
self::assertInstanceOf(expected: MySQLDockerContainer::class, actual: $container);
}

public function testStopOnShutdownDelegatesToUnderlyingContainer(): void
{
/** @Given a ShutdownHook that tracks registration */
Expand Down
Loading