Skip to content

Commit

Permalink
Merge pull request #286 from tonysm/start-stop-dx
Browse files Browse the repository at this point in the history
Improve Start and Stop DX
  • Loading branch information
mattstauffer authored May 30, 2023
2 parents 2e304df + d844d83 commit 56ef95c
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 17 deletions.
2 changes: 1 addition & 1 deletion app/Commands/DisableCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function disableByServiceName(string $service): void
$serviceContainerId = $serviceMatches->flip()->first();
break;
default: // > 1
$serviceContainerId = $this->selectMenu($disableableServices ?? $this->disableableServices);
$serviceContainerId = $this->selectMenu($this->disableableServices);

if (! $serviceContainerId) {
return;
Expand Down
59 changes: 51 additions & 8 deletions app/Commands/StartCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function handle(Docker $docker, Environment $environment): void

if (filled($containers)) {
foreach ($containers as $container) {
$this->start($container);
$this->startByServiceNameOrContainerId($container);
}

return;
Expand Down Expand Up @@ -66,6 +66,51 @@ public function startableContainers(): array
}, collect())->toArray();
}

public function startByServiceNameOrContainerId(string $serviceNameOrContainerId): void
{
$containersByServiceName = $this->docker->startableTakeoutContainers()
->map(function ($container) {
return [
'container' => $container,
'label' => str_replace('TO--', '', $container['names']),
];
})
->filter(function ($item) use ($serviceNameOrContainerId) {
return Str::startsWith($item['label'], $serviceNameOrContainerId);
});

// If we don't get any container by the service name, that probably means
// the user is trying to start a container using its container ID, so
// we will just forward that down to the underlying start method.

if ($containersByServiceName->isEmpty()) {
$this->start($serviceNameOrContainerId);

return;
}

if ($containersByServiceName->count() === 1) {
$this->start($containersByServiceName->first()['container']['container_id']);

return;
}

$selectedItem = $this->loadMenu($containersByServiceName->map(function ($item) {
$label = $item['container']['container_id'] . ' - ' . $item['label'];

return [
$label,
$this->loadMenuItem($item['container'], $label),
];
})->all());

if (! $selectedItem) {
return;
}

$this->start($selectedItem);
}

public function start(string $container): void
{
if (Str::contains($container, ' -')) {
Expand All @@ -75,20 +120,18 @@ public function start(string $container): void
$this->docker->startContainer($container);
}

private function loadMenu($startableContainers): void
private function loadMenu($startableContainers)
{
if ($this->environment->isWindowsOs()) {
$this->windowsMenu($startableContainers);

return;
return $this->windowsMenu($startableContainers);
}

$this->defaultMenu($startableContainers);
return $this->defaultMenu($startableContainers);
}

private function defaultMenu($startableContainers)
{
$this->menu(self::MENU_TITLE)
return $this->menu(self::MENU_TITLE)
->addItems($startableContainers)
->addLineBreak('', 1)
->open();
Expand Down Expand Up @@ -116,7 +159,7 @@ private function windowsMenu($startableContainers)
return $value[0] === $choice;
});

call_user_func(array_values($chosenStartableContainer)[0][1]);
return call_user_func(array_values($chosenStartableContainer)[0][1]);
}

private function loadMenuItem($container, $label): callable
Expand Down
54 changes: 46 additions & 8 deletions app/Commands/StopCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function handle(Docker $docker, Environment $environment): void

if (filled($containers)) {
foreach ($containers as $container) {
$this->stop($container);
$this->stopByServiceNameOrContainerId($container);
}

return;
Expand Down Expand Up @@ -66,6 +66,46 @@ public function stoppableContainers(): array
}, collect())->toArray();
}

public function stopByServiceNameOrContainerId(string $serviceNameOrContainerId): void
{
$containersByServiceName = $this->docker->stoppableTakeoutContainers()
->map(function ($container) {
return [
'container' => $container,
'label' => str_replace('TO--', '', $container['names']),
];
})
->filter(function ($item) use ($serviceNameOrContainerId) {
return Str::startsWith($item['label'], $serviceNameOrContainerId);
});

if ($containersByServiceName->isEmpty()) {
$this->start($serviceNameOrContainerId);

return;
}

if ($containersByServiceName->count() === 1) {
$this->stop($containersByServiceName->first()['container']['container_id']);
return;
}

$selectedContainer = $this->loadMenu($containersByServiceName->map(function ($item) {
$label = $item['container']['container_id'] . ' - ' . $item['label'];

return [
$label,
$this->loadMenuItem($item['container'], $label),
];
})->all());

if (! $selectedContainer) {
return;
}

$this->stop($selectedContainer);
}

public function stop(string $container): void
{
if (Str::contains($container, ' -')) {
Expand All @@ -75,20 +115,18 @@ public function stop(string $container): void
$this->docker->stopContainer($container);
}

private function loadMenu($stoppableContainers): void
private function loadMenu($stoppableContainers)
{
if ($this->environment->isWindowsOs()) {
$this->windowsMenu($stoppableContainers);

return;
return $this->windowsMenu($stoppableContainers);
}

$this->defaultMenu($stoppableContainers);
return $this->defaultMenu($stoppableContainers);
}

private function defaultMenu($stoppableContainers)
{
$this->menu(self::MENU_TITLE)
return $this->menu(self::MENU_TITLE)
->addItems($stoppableContainers)
->addLineBreak('', 1)
->open();
Expand Down Expand Up @@ -116,7 +154,7 @@ private function windowsMenu($stoppableContainers)
return $value[0] === $choice;
});

call_user_func(array_values($chosenStoppableContainer)[0][1]);
return call_user_func(array_values($chosenStoppableContainer)[0][1]);
}

private function loadMenuItem($container, $label): callable
Expand Down
85 changes: 85 additions & 0 deletions tests/Feature/StartCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,89 @@ function (string $title) use ($menuMock) {
$this->artisan('start');
}
}

/** @test */
function it_can_start_containers_by_name()
{
$services = Collection::make([
[
'container_id' => $containerId = '12345',
'names' => 'TO--mysql--8.0.22--3306',
'status' => 'Exited (0) 8 days ago',
'ports' => '',
'base_alias' => 'mysql',
'full_alias' => 'mysql8.0',
],
]);

$this->mock(Docker::class, function ($mock) use ($services, $containerId) {
$mock->shouldReceive('isInstalled')->andReturn(true);
$mock->shouldReceive('isDockerServiceRunning')->andReturn(true);
$mock->shouldReceive('startableTakeoutContainers')->andReturn($services, new Collection);
$mock->shouldReceive('startContainer')->once()->with($containerId);
});

$this->artisan('start', ['containerId' => ['mysql']])
->assertExitCode(0);
}

/** @test */
function it_can_start_containers_by_name_when_there_are_multiple()
{
$services = Collection::make([
[
'container_id' => $firstContainerId = '12345',
'names' => $firstContainerName = 'TO--mysql--8.0.22--3306',
'status' => 'Exited (0) 8 days ago',
'ports' => '',
'base_alias' => 'mysql',
'full_alias' => 'mysql8.0',
],
[
'container_id' => $secondContainerId = '67890',
'names' => $secondContainerName = 'TO--mysql--8.0.20-3306',
'status' => 'Exited (0) 8 days ago',
'ports' => '',
'base_alias' => 'mysql',
'full_alias' => 'mysql8.0',
],
]);

$this->mock(Docker::class, function ($mock) use ($services, $secondContainerId) {
$mock->shouldReceive('isInstalled')->andReturn(true);
$mock->shouldReceive('isDockerServiceRunning')->andReturn(true);
$mock->shouldReceive('startableTakeoutContainers')->andReturn($services, new Collection);
$mock->shouldReceive('startContainer')->with($secondContainerId)->once();
});

$menuItems = [
$firstContainerId . ' - ' . $firstContainerName,
$mysql = $secondContainerId . ' - ' . $secondContainerName,
'<info>Exit</>',
];

if ($this->isWindows()) {
$this->artisan('start')
->expectsChoice('Takeout containers to start', $mysql, $menuItems)
->assertExitCode(0);
} else {
$menuMock = $this->mock(Menu::class, function ($mock) use ($mysql) {
$mock->shouldReceive('setTitleSeparator')->andReturnSelf();
$mock->shouldReceive('addItems')->andReturnSelf();
$mock->shouldReceive('addLineBreak')->andReturnSelf();
$mock->shouldReceive('open')->andReturn($mysql)->once();
});

Command::macro(
'menu',
function (string $title) use ($menuMock) {
Assert::assertEquals('Takeout containers to start', $title);

return $menuMock;
}
);

$this->artisan('start', ['containerId' => ['mysql']]);
}
}
}
85 changes: 85 additions & 0 deletions tests/Feature/StopCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,89 @@ function (string $title) use ($menuMock, $services) {
$this->artisan('stop');
}
}

/** @test */
function it_can_stop_containers_by_service_name()
{
$services = Collection::make([
[
'container_id' => $containerId = '12345',
'names' => 'TO--mysql--8.0.22--3306',
'status' => 'Up 27 minutes',
'ports' => '0.0.0.0:3306->3306/tcp, 33060/tcp',
'base_alias' => 'mysql',
'full_alias' => 'mysql8.0',
],
]);

$this->mock(Docker::class, function ($mock) use ($services, $containerId) {
$mock->shouldReceive('isInstalled')->andReturn(true);
$mock->shouldReceive('isDockerServiceRunning')->andReturn(true);
$mock->shouldReceive('stoppableTakeoutContainers')->andReturn($services, new Collection);
$mock->shouldReceive('stopContainer')->with($containerId)->once();
});

$this->artisan('stop', ['containerId' => ['mysql']])
->assertExitCode(0);
}

/** @test */
function it_can_stop_a_service_from_menu_when_there_are_multiple()
{
$services = Collection::make([
[
'container_id' => $firstContainerId = '12345',
'names' => $firstContainerName = 'TO--mysql--8.0.22--3306',
'status' => 'Up 27 minutes',
'ports' => '0.0.0.0:3306->3306/tcp, 33060/tcp',
'base_alias' => 'mysql',
'full_alias' => 'mysql8.0',
],
[
'container_id' => $secondContainerId = '67890',
'names' => $secondContainerName = 'TO--mysql--8.0.20--3306',
'status' => 'Up 27 minutes',
'ports' => '0.0.0.0:3306->3306/tcp, 33060/tcp',
'base_alias' => 'mysql',
'full_alias' => 'mysql8.0',
],
]);

$menuItems = [
$firstContainerId . ' - ' . $firstContainerName,
$mysql = $secondContainerId . ' - ' . $secondContainerName,
'<info>Exit</>',
];

$this->mock(Docker::class, function ($mock) use ($services, $secondContainerId) {
$mock->shouldReceive('isInstalled')->andReturn(true);
$mock->shouldReceive('isDockerServiceRunning')->andReturn(true);
$mock->shouldReceive('stoppableTakeoutContainers')->andReturn($services, new Collection);
$mock->shouldReceive('stopContainer')->with($secondContainerId)->once();
});

if ($this->isWindows()) {
$this->artisan('stop')
->expectsChoice('Takeout containers to stop', $mysql, $menuItems)
->assertExitCode(0);
} else {
$menuMock = $this->mock(Menu::class, function ($mock) use ($mysql) {
$mock->shouldReceive('setTitleSeparator')->andReturnSelf();
$mock->shouldReceive('addItems')->andReturnSelf();
$mock->shouldReceive('addLineBreak')->andReturnSelf();
$mock->shouldReceive('open')->andReturn($mysql)->once();
});

Command::macro(
'menu',
function (string $title) use ($menuMock, $services) {
Assert::assertEquals('Takeout containers to stop', $title);

return $menuMock;
}
);

$this->artisan('stop', ['containerId' => ['mysql']]);
}
}
}

0 comments on commit 56ef95c

Please sign in to comment.