Implement Keystone environment deployments
This commit is contained in:
85
app/Actions/Services/ResolveServiceImageDigest.php
Normal file
85
app/Actions/Services/ResolveServiceImageDigest.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Services;
|
||||
|
||||
use App\Drivers\Concerns\RendersCompose;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Services\Operations\RemoteCommandRunner;
|
||||
use RuntimeException;
|
||||
|
||||
class ResolveServiceImageDigest
|
||||
{
|
||||
public function __construct(
|
||||
private readonly RemoteCommandRunner $remoteCommandRunner,
|
||||
) {}
|
||||
|
||||
public function execute(Service $service): string
|
||||
{
|
||||
$image = $this->imageReference($service);
|
||||
|
||||
if (str_starts_with($image, 'sha256:')) {
|
||||
return $image;
|
||||
}
|
||||
|
||||
$output = $this->remoteCommandRunner->run($this->targetServer($service), implode("\n", [
|
||||
'set -euo pipefail',
|
||||
'image='.escapeshellarg($image),
|
||||
'digest=$(docker image inspect --format '.escapeshellarg('{{if .RepoDigests}}{{index .RepoDigests 0}}{{else}}{{.Id}}{{end}}').' "$image" 2>/dev/null || true)',
|
||||
'if [ -z "$digest" ]; then',
|
||||
' docker pull "$image"',
|
||||
' digest=$(docker image inspect --format '.escapeshellarg('{{if .RepoDigests}}{{index .RepoDigests 0}}{{else}}{{.Id}}{{end}}').' "$image")',
|
||||
'fi',
|
||||
'printf "image_digest=%s\n" "$digest"',
|
||||
]));
|
||||
|
||||
if (preg_match('/image_digest=(?<digest>\S+)/', $output, $matches)) {
|
||||
return $this->digestFromOutput($matches['digest'], $image);
|
||||
}
|
||||
|
||||
return $this->digestFromOutput($output, $image);
|
||||
}
|
||||
|
||||
private function imageReference(Service $service): string
|
||||
{
|
||||
$driver = $service->driver();
|
||||
|
||||
if (! $driver instanceof RendersCompose) {
|
||||
throw new RuntimeException("Driver [{$service->driver_name}] cannot resolve an image digest.");
|
||||
}
|
||||
|
||||
$image = $driver->composeService()['image'] ?? null;
|
||||
|
||||
if (! is_string($image) || $image === '') {
|
||||
throw new RuntimeException("Driver [{$service->driver_name}] did not provide an image reference.");
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
private function targetServer(Service $service): Server
|
||||
{
|
||||
$service->loadMissing('server', 'replicas.server');
|
||||
|
||||
$server = $service->replicas->first()?->server ?: $service->server;
|
||||
|
||||
if (! $server instanceof Server) {
|
||||
throw new RuntimeException("Service [{$service->id}] must have a target server before resolving an image digest.");
|
||||
}
|
||||
|
||||
return $server;
|
||||
}
|
||||
|
||||
private function digestFromOutput(string $output, string $image): string
|
||||
{
|
||||
if (str_contains($output, '@')) {
|
||||
return str($output)->after('@')->trim()->value();
|
||||
}
|
||||
|
||||
if (str_starts_with($output, 'sha256:')) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unable to resolve image digest for [{$image}].");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user