Implement Keystone environment deployments
This commit is contained in:
149
app/Services/Compose/ComposeRenderer.php
Normal file
149
app/Services/Compose/ComposeRenderer.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Compose;
|
||||
|
||||
use App\Drivers\Concerns\RendersCompose;
|
||||
use App\Models\Service;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class ComposeRenderer
|
||||
{
|
||||
public function render(Service $service): string
|
||||
{
|
||||
$driver = $service->driver();
|
||||
|
||||
if (! $driver instanceof RendersCompose) {
|
||||
throw new InvalidArgumentException("Driver [{$service->driver_name}] cannot render Docker Compose.");
|
||||
}
|
||||
|
||||
$composeService = $driver->composeService();
|
||||
|
||||
if ($service->default_cpu_limit && ! isset($composeService['cpus'])) {
|
||||
$composeService['cpus'] = (string) $service->default_cpu_limit;
|
||||
}
|
||||
|
||||
if ($service->default_memory_limit_mb && ! isset($composeService['mem_limit'])) {
|
||||
$composeService['mem_limit'] = "{$service->default_memory_limit_mb}m";
|
||||
$composeService['memswap_limit'] = "{$service->default_memory_limit_mb}m";
|
||||
}
|
||||
|
||||
$document = [
|
||||
'services' => [
|
||||
$this->serviceKey($service) => $composeService,
|
||||
],
|
||||
];
|
||||
|
||||
$volumes = array_filter($driver->composeVolumes(), fn (mixed $volume): bool => $volume !== false);
|
||||
|
||||
if ($volumes !== []) {
|
||||
$document['volumes'] = $volumes;
|
||||
}
|
||||
|
||||
return $this->toYaml($document);
|
||||
}
|
||||
|
||||
public function renderEnvironmentFile(Service $service): string
|
||||
{
|
||||
$driver = $service->driver();
|
||||
|
||||
if (! method_exists($driver, 'environmentExports')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return collect($driver->environmentExports())
|
||||
->map(fn (mixed $value, string $key): string => $key.'='.$this->formatEnvValue($value))
|
||||
->implode("\n");
|
||||
}
|
||||
|
||||
private function serviceKey(Service $service): string
|
||||
{
|
||||
return str($service->name)->slug('_')->value() ?: 'service';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $document
|
||||
*/
|
||||
private function toYaml(array $document, int $indent = 0): string
|
||||
{
|
||||
$lines = [];
|
||||
|
||||
foreach ($document as $key => $value) {
|
||||
$prefix = str_repeat(' ', $indent);
|
||||
|
||||
if (is_array($value)) {
|
||||
if ($value === []) {
|
||||
$lines[] = "{$prefix}{$key}: []";
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines[] = "{$prefix}{$key}:";
|
||||
$lines[] = $this->arrayToYaml($value, $indent + 1);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
$lines[] = "{$prefix}{$key}: {}";
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines[] = "{$prefix}{$key}: ".$this->formatScalar($value);
|
||||
}
|
||||
|
||||
return implode("\n", array_filter($lines))."\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $items
|
||||
*/
|
||||
private function arrayToYaml(array $items, int $indent): string
|
||||
{
|
||||
if (array_is_list($items)) {
|
||||
return $this->listToYaml($items, $indent);
|
||||
}
|
||||
|
||||
return $this->toYaml($items, $indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $items
|
||||
*/
|
||||
private function listToYaml(array $items, int $indent): string
|
||||
{
|
||||
$lines = [];
|
||||
$prefix = str_repeat(' ', $indent);
|
||||
|
||||
foreach ($items as $item) {
|
||||
if (is_array($item)) {
|
||||
$lines[] = "{$prefix}-";
|
||||
$lines[] = $this->arrayToYaml($item, $indent + 1);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines[] = "{$prefix}- ".$this->formatScalar($item);
|
||||
}
|
||||
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
private function formatScalar(mixed $value): string
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
return $value ? 'true' : 'false';
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
return '"'.str_replace('"', '\"', (string) $value).'"';
|
||||
}
|
||||
|
||||
private function formatEnvValue(mixed $value): string
|
||||
{
|
||||
return str_replace(["\n", "\r"], ['\n', ''], (string) $value);
|
||||
}
|
||||
}
|
||||
10
app/Services/Operations/RemoteCommandRunner.php
Normal file
10
app/Services/Operations/RemoteCommandRunner.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Operations;
|
||||
|
||||
use App\Models\Server;
|
||||
|
||||
interface RemoteCommandRunner
|
||||
{
|
||||
public function run(Server $server, string $script): string;
|
||||
}
|
||||
20
app/Services/Operations/SshRemoteCommandRunner.php
Normal file
20
app/Services/Operations/SshRemoteCommandRunner.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Operations;
|
||||
|
||||
use App\Models\Server;
|
||||
use RuntimeException;
|
||||
|
||||
class SshRemoteCommandRunner implements RemoteCommandRunner
|
||||
{
|
||||
public function run(Server $server, string $script): string
|
||||
{
|
||||
$result = $server->sshClient()->execute($script);
|
||||
|
||||
if (! $result->isSuccessful()) {
|
||||
throw new RuntimeException(trim($result->getErrorOutput()) ?: 'Remote command failed.');
|
||||
}
|
||||
|
||||
return trim($result->getOutput());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user