normalized image list * pull POST {"reference": "docker.io/library/postgres:16"} * remove POST {"id": "...", "force": false} * prune POST {} -> removes every image not used by any container * tag POST {"id": "...", "repo": "...", "tag": "latest"} */ declare(strict_types=1); require __DIR__ . '/../include/bootstrap.php'; $action = $_GET['action'] ?? ''; switch ($action) { case 'list': podman_json_response(images_list($client)); break; case 'pull': $body = podman_read_json_body(); $reference = (string) ($body['reference'] ?? ''); if ($reference === '') { podman_json_error('Missing reference in request body', 400); } podman_json_response($client->pullImage($reference)); break; case 'remove': $body = podman_read_json_body(); $id = (string) ($body['id'] ?? ''); if ($id === '') { podman_json_error('Missing id in request body', 400); } $client->removeImage($id, (bool) ($body['force'] ?? false)); podman_json_response(['status' => 'removed']); break; case 'prune': $removed = $client->pruneImages(); $reclaimed = 0; foreach ($removed as $r) { $reclaimed += (int) ($r['Size'] ?? 0); } podman_json_response(['removedCount' => count($removed), 'reclaimedBytes' => $reclaimed]); break; case 'tag': $body = podman_read_json_body(); $id = (string) ($body['id'] ?? ''); $repo = trim((string) ($body['repo'] ?? '')); $tag = trim((string) ($body['tag'] ?? '')) ?: 'latest'; if ($id === '' || $repo === '') { podman_json_error('Missing id or repo in request body', 400); } $client->tagImage($id, $repo, $tag); podman_json_response(['status' => 'tagged']); break; default: podman_json_error("Unknown action '{$action}'", 400); } /** @return array> */ function images_list(PodmanClient $client): array { $raw = $client->listImages(); // In-use counts let the frontend show "0" (safe to remove) vs a // positive count, without a separate round trip per image. $usageCounts = []; foreach ($client->listContainers(true) as $c) { $imageId = (string) ($c['ImageID'] ?? ''); if ($imageId !== '') { $usageCounts[$imageId] = ($usageCounts[$imageId] ?? 0) + 1; } } $out = []; foreach ($raw as $img) { $id = (string) ($img['Id'] ?? ''); $repoTags = $img['RepoTags'] ?? []; $repository = ''; $tag = ''; if (is_array($repoTags) && count($repoTags) > 0 && is_string($repoTags[0]) && str_contains($repoTags[0], ':')) { [$repository, $tag] = explode(':', $repoTags[0], 2); } $out[] = [ 'id' => $id, 'shortId' => podman_short_id($id), 'repository' => $repository, 'tag' => $tag, 'sizeBytes' => (int) ($img['Size'] ?? 0), 'sizeFormatted' => podman_format_bytes((int) ($img['Size'] ?? 0)), // Unlike containers' Created/StartedAt (RFC3339 strings), libpod // reports image Created as a Unix timestamp integer directly. 'createdAt' => isset($img['Created']) ? (int) $img['Created'] : null, 'usedBy' => $usageCounts[$id] ?? 0, ]; } usort($out, static fn($a, $b) => strcmp($a['repository'], $b['repository'])); return $out; }