pods with nested container summaries * create POST {"name": "...", "ports": [{"hostPort": 8080, "containerPort": 80, "protocol": "tcp"}]} * start POST {"name": "..."} * stop POST {"name": "...", "timeout": 10} * restart POST {"name": "...", "timeout": 10} * remove POST {"name": "...", "force": false} */ declare(strict_types=1); require __DIR__ . '/../include/bootstrap.php'; $action = $_GET['action'] ?? ''; switch ($action) { case 'list': podman_json_response(pods_list($client)); break; case 'create': $body = podman_read_json_body(); $id = $client->createPod(build_pod_spec($body)); podman_json_response(['id' => $id, 'status' => 'created']); break; case 'start': $body = podman_read_json_body(); $client->startPod(require_name($body)); podman_json_response(['status' => 'started']); break; case 'stop': $body = podman_read_json_body(); $client->stopPod(require_name($body), (int) ($body['timeout'] ?? $podmanConfig->stopTimeoutSeconds)); podman_json_response(['status' => 'stopped']); break; case 'restart': $body = podman_read_json_body(); $client->restartPod(require_name($body), (int) ($body['timeout'] ?? $podmanConfig->stopTimeoutSeconds)); podman_json_response(['status' => 'restarted']); break; case 'remove': $body = podman_read_json_body(); $client->removePod(require_name($body), (bool) ($body['force'] ?? false)); podman_json_response(['status' => 'removed']); break; default: podman_json_error("Unknown action '{$action}'", 400); } /** * Builds a libpod pod-create body from the "New Pod" form fields. Verified * live against a real podman system service — {"name": "...", * "portmappings": [...]} creates a pod with a shared infra container whose * port bindings apply to every member container. * * @param array $body * @return array */ function build_pod_spec(array $body): array { $name = trim((string) ($body['name'] ?? '')); if ($name === '') { podman_json_error('Missing name in request body', 400); } // Same character set podman enforces for container names (define.NameRegex // in libpod applies to pods too) — validated here for the same reason // ajax/containers.php validates it: a clear message instead of podman's // raw "names must match ...: invalid argument". if (preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/', $name) !== 1) { podman_json_error( "Pod name (\"{$name}\") can only contain letters, digits, \".\", \"_\", \"-\" — no spaces. Try \"" . preg_replace('/[^a-zA-Z0-9_.-]+/', '-', $name) . '" instead.', 400 ); } $spec = ['name' => $name]; $ports = []; foreach (($body['ports'] ?? []) as $row) { $hostPort = (int) ($row['hostPort'] ?? 0); $containerPort = (int) ($row['containerPort'] ?? 0); if ($hostPort > 0 && $containerPort > 0) { $ports[] = [ 'host_ip' => '', 'host_port' => $hostPort, 'container_port' => $containerPort, 'protocol' => (string) ($row['protocol'] ?? 'tcp'), ]; } } if ($ports !== []) { $spec['portmappings'] = $ports; } return $spec; } /** @param array $body */ function require_name(array $body): string { $name = (string) ($body['name'] ?? ''); if ($name === '') { podman_json_error('Missing name in request body', 400); } return $name; } /** @return array> */ function pods_list(PodmanClient $client): array { $pods = $client->listPods(); $containersByPod = []; foreach (containers_grouped_by_pod($client) as $podId => $members) { $containersByPod[$podId] = $members; } $out = []; foreach ($pods as $p) { $id = (string) ($p['Id'] ?? ''); $out[] = [ 'id' => $id, 'name' => (string) ($p['Name'] ?? ''), 'status' => strtolower((string) ($p['Status'] ?? 'unknown')), 'containersTotal' => (int) ($p['NumContainers'] ?? count($containersByPod[$id] ?? [])), 'infraId' => $p['InfraId'] ?? null, 'members' => $containersByPod[$id] ?? [], ]; } return $out; } /** @return array>> keyed by pod id */ function containers_grouped_by_pod(PodmanClient $client): array { $grouped = []; foreach ($client->listContainers(true) as $c) { $podId = $c['Pod'] ?? null; if (!is_string($podId) || $podId === '') { continue; } $names = $c['Names'] ?? []; $name = is_array($names) && count($names) > 0 ? ltrim((string) $names[0], '/') : podman_short_id((string) ($c['Id'] ?? '')); $grouped[$podId][] = [ 'id' => (string) ($c['Id'] ?? ''), 'name' => $name, 'image' => (string) ($c['Image'] ?? ''), 'state' => strtolower((string) ($c['State'] ?? 'unknown')), ]; } return $grouped; }