- Выделена страница списка кластеров, панель упрощена; nav_active и крошки ведут в раздел Кластеры; theme.js синхронизирует активную пилюлю по URL. - Доработки дашборда, аддонов, журнала, стилей и API-документации. - Поддержка Podman: docker-compose.podman.yml, скрипты сокета; Makefile и env.
186 lines
6.8 KiB
Python
186 lines
6.8 KiB
Python
#!/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 "<no value>" 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()
|