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=(?\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}]."); } }