#!/usr/bin/env python3 """Печатает в stdout путь к ``podman.sock`` или ``uid:gid`` владельца сокета. Режимы: * без аргументов — путь (для ``$(shell ...)`` в Makefile); * ``--print-owner`` — ``uid:gid`` по ``stat`` найденного сокета (для ``KIND_K8S_CONTAINER_UIDGID``). Логика пути: ``podman info`` (схема ``unix://`` снимается), ``podman machine inspect`` (без путей в ``/var/folders/…`` на macOS — там API-сокет, **compose не смонтирует**: ``operation not supported``), стабильные ``*.sock`` под ``~/.local/share/containers/podman/machine/``, затем типичные пути пользователя. Автор: Сергей Антропов Сайт: https://devops.org.ru """ from __future__ import annotations import json import os import shutil import subprocess import sys from pathlib import Path def _compose_cannot_bind_mount_socket(path_str: str) -> bool: """ На macOS сокет в ``/var/folders/…/T/podman/`` (например ``*-api.sock`` из machine inspect) существует на диске, но bind-mount в контейнер через compose часто даёт ``mkdir … operation not supported`` — такой путь в ``CONTAINER_SOCKET`` использовать нельзя. """ if sys.platform != "darwin": return False norm = os.path.normpath(path_str) return "/var/folders/" in norm def _strip_unix_scheme(raw: str) -> str: """Путь на диске без префикса ``unix://`` / ``unix:`` (как в выводе ``podman info``).""" s = (raw or "").strip() if s.startswith("unix://"): return s[7:].strip() or s if s.startswith("unix:"): rest = s[5:].lstrip("/") return "/" + rest if rest else s return s def _podman_machine_socket_paths(podman_bin: str) -> list[str]: """ Пути к сокету на **хосте** из ``podman machine inspect`` (актуально для macOS/Windows). На Linux без machine обычно возвращается ``[]``. """ try: proc = subprocess.run( [podman_bin, "machine", "inspect"], capture_output=True, text=True, timeout=20, ) if proc.returncode != 0 or not (proc.stdout or "").strip(): return [] data = json.loads(proc.stdout) if not isinstance(data, list): return [] out: list[str] = [] for m in data: if not isinstance(m, dict): continue ci = m.get("ConnectionInfo") or {} if not isinstance(ci, dict): continue ps = ci.get("PodmanSocket") pth: str | None = None if isinstance(ps, dict): raw = ps.get("Path") pth = str(raw).strip() if raw else None elif isinstance(ps, str) and ps.strip(): pth = ps.strip() if pth: cleaned = _strip_unix_scheme(pth) if not _compose_cannot_bind_mount_socket(cleaned): out.append(cleaned) return out except (OSError, subprocess.TimeoutExpired, json.JSONDecodeError, TypeError, ValueError): return [] def _darwin_stable_machine_sockets() -> list[str]: """Подкаталог machine на macOS: сокеты вне временного ``/var/folders``.""" base = Path.home() / ".local/share/containers/podman/machine" if sys.platform != "darwin" or not base.is_dir(): return [] out: list[str] = [] try: for p in sorted(base.rglob("*.sock"), key=lambda x: (len(x.parts), str(x))): s = str(p) if _compose_cannot_bind_mount_socket(s): continue out.append(s) except OSError: return [] return out def _podman_socket_path_for_user() -> str: xdg = (os.environ.get("XDG_RUNTIME_DIR") or "").strip() if xdg: return f"{xdg.rstrip('/')}/podman/podman.sock" try: return f"/run/user/{os.getuid()}/podman/podman.sock" except AttributeError: return "/run/user/1000/podman/podman.sock" def podman_socket_owner_uidgid() -> str: """ UID:GID владельца найденного ``podman.sock`` на диске (``stat``). Нужен для ``user:`` в compose: при Podman на macOS и в VM владелец сокета часто **не совпадает** с ``id -u`` / ``id -g`` процесса ``make`` на хосте — иначе ``permission denied`` на смонтированный сокет внутри контейнера. Если сокет недоступен — fallback на UID:GID текущего процесса. """ path_str = detect_podman_socket_path() p = Path(path_str) try: if p.is_socket(): st = p.stat() return f"{st.st_uid}:{st.st_gid}" except OSError: pass try: return f"{os.getuid()}:{os.getgid()}" except AttributeError: return "1000:1000" def detect_podman_socket_path() -> str: """Путь к существующему сокету или типичный путь по умолчанию.""" cands: list[str] = [] podman_bin = shutil.which("podman") if podman_bin: try: proc = subprocess.run( [podman_bin, "info", "-f", "{{.Host.RemoteSocket.Path}}"], capture_output=True, text=True, timeout=15, ) if proc.returncode == 0: line = (proc.stdout or "").strip().splitlines() candidate = (line[0] if line else "").strip() if candidate and "" not in candidate.lower(): cands.append(_strip_unix_scheme(candidate)) except (OSError, subprocess.TimeoutExpired): pass # Реальный файл на хосте (без macOS temp api.sock — см. _compose_cannot_bind_mount_socket). cands.extend(_podman_machine_socket_paths(podman_bin)) cands.extend(_darwin_stable_machine_sockets()) cands.append(_podman_socket_path_for_user()) for path in cands: cleaned = _strip_unix_scheme(path) if _compose_cannot_bind_mount_socket(cleaned): continue p = Path(cleaned).expanduser() try: if p.is_socket(): return str(p.resolve()) except OSError: continue return str(Path(_strip_unix_scheme(_podman_socket_path_for_user())).expanduser()) def main() -> None: if len(sys.argv) > 1 and sys.argv[1] == "--print-owner": sys.stdout.write(podman_socket_owner_uidgid()) return sys.stdout.write(detect_podman_socket_path()) if __name__ == "__main__": main()