Files
docker-migrate/discover/docker.py
T

226 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
docker.py — Discovery Docker контейнеров, compose, volumes, сети
"""
import json
import os
import re
from core.runner import run, run_json, exists
from core.color import info, warn, success, log_cmd
def find_container(name_or_id):
"""
Ищет контейнер по имени (частичное совпадение) или ID.
Возвращает tuple (container_id, container_name, inspect_dict).
"""
if not exists("docker"):
raise RuntimeError("Docker не найден на сервере")
# Сначала exact id или name
try:
out = run_json(f"docker inspect --type=container {name_or_id}")
if out and len(out) > 0:
c = out[0]
return c["Id"], c["Name"].lstrip("/"), c
except RuntimeError:
pass
# Частичный поиск по имени
ps_out = run("docker ps -a --format json", check=False)
lines = [ln for ln in ps_out.stdout.strip().splitlines() if ln.strip()]
matches = []
for ln in lines:
try:
item = json.loads(ln)
names = item.get("Names", "")
image = item.get("Image", "")
cid = item.get("ID", "")
if name_or_id.lower() in names.lower() or name_or_id.lower() in image.lower():
matches.append((cid, names))
except json.JSONDecodeError:
continue
if not matches:
raise RuntimeError(f"Контейнер '{name_or_id}' не найден среди docker ps -a")
if len(matches) == 1:
cid, cname = matches[0]
out = run_json(f"docker inspect {cid}")
c = out[0]
return c["Id"], c["Name"].lstrip("/"), c
else:
from core.color import prompt
print("Найдено несколько контейнеров:")
for i, (cid, cname) in enumerate(matches, 1):
print(f" {i}. {cname} ({cid[:12]})")
sel = prompt("Выберите номер")
idx = int(sel) - 1
cid, cname = matches[idx]
out = run_json(f"docker inspect {cid}")
c = out[0]
return c["Id"], c["Name"].lstrip("/"), c
def get_compose_file_from_container(inspect):
"""
Пытается найти compose-файл по Labels или по рабочей директории контейнера.
"""
labels = inspect.get("Config", {}).get("Labels", {}) or {}
# Compose labels: com.docker.compose.project.working_dir
working_dir = labels.get("com.docker.compose.project.working_dir")
compose_file = labels.get("com.docker.compose.project.config_files")
if compose_file and os.path.isfile(compose_file):
return compose_file
# Fallback: ищем docker-compose.yml / compose.yml рядом с working_dir
if working_dir and os.path.isdir(working_dir):
for fname in ("docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"):
fp = os.path.join(working_dir, fname)
if os.path.isfile(fp):
return fp
# Fallback по Env (PWD если контейнер запущен через compose)
env = inspect.get("Config", {}).get("Env", [])
pwd = None
for e in env:
if e.startswith("PWD="):
pwd = e.split("=", 1)[1]
break
if pwd and os.path.isdir(pwd):
for fname in ("docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"):
fp = os.path.join(pwd, fname)
if os.path.isfile(fp):
return fp
return None
def get_env_file(compose_path, inspect):
"""
Ищет .env рядом с compose-файлом или в working_dir.
"""
candidates = []
if compose_path:
candidates.append(os.path.join(os.path.dirname(compose_path), ".env"))
labels = inspect.get("Config", {}).get("Labels", {}) or {}
wd = labels.get("com.docker.compose.project.working_dir")
if wd:
candidates.append(os.path.join(wd, ".env"))
env_list = inspect.get("Config", {}).get("Env", [])
pwd = None
for e in env_list:
if e.startswith("PWD="):
pwd = e.split("=", 1)[1]
break
if pwd:
candidates.append(os.path.join(pwd, ".env"))
for c in candidates:
if os.path.isfile(c):
return c
return None
def get_mounts_and_volumes(inspect):
"""
Возвращает список dict: {type: bind|volume|tmpfs, source, destination, mode}
"""
mounts = inspect.get("Mounts", []) or []
result = []
for m in mounts:
result.append({
"type": m.get("Type", "unknown"),
"source": m.get("Source", ""),
"destination": m.get("Destination", ""),
"mode": m.get("Mode", ""),
})
return result
def get_ports(inspect):
"""
Возвращает словарь host_port -> container_port/proto.
"""
hostconfig = inspect.get("HostConfig", {})
port_bindings = hostconfig.get("PortBindings", {}) or {}
result = {}
for container_port_proto, bindings in port_bindings.items():
if bindings:
for b in bindings:
hp = b.get("HostPort")
ha = b.get("HostIp", "0.0.0.0")
result[f"{ha}:{hp}"] = container_port_proto
return result
def get_networks(inspect):
nets = inspect.get("NetworkSettings", {}).get("Networks", {}) or {}
return list(nets.keys())
def get_host_config_flags(inspect):
hc = inspect.get("HostConfig", {})
return {
"network_mode": hc.get("NetworkMode"),
"privileged": hc.get("Privileged"),
"cap_add": hc.get("CapAdd", []),
"devices": hc.get("Devices", []),
"restart_policy": hc.get("RestartPolicy", {}),
"pid_mode": hc.get("PidMode"),
"ipc_mode": hc.get("IpcMode"),
}
def get_image(inspect):
return inspect.get("Config", {}).get("Image", inspect.get("Image", ""))
def discover_docker(name_or_id):
"""
Главная функция. Возвращает dict с полной информацией docker-части.
"""
info(f"Ищем Docker контейнер: {name_or_id} ...")
cid, cname, inspect = find_container(name_or_id)
success(f"Найден контейнер: {cname} ({cid[:12]})")
compose_file = get_compose_file_from_container(inspect)
env_file = get_env_file(compose_file, inspect) if compose_file else None
mounts = get_mounts_and_volumes(inspect)
ports = get_ports(inspect)
networks = get_networks(inspect)
hostcfg = get_host_config_flags(inspect)
image = get_image(inspect)
data = {
"container_id": cid,
"container_name": cname,
"image": image,
"status": inspect.get("State", {}).get("Status", "unknown"),
"compose_file": compose_file,
"env_file": env_file,
"mounts": mounts,
"ports": ports,
"networks": networks,
"host_config": hostcfg,
"labels": inspect.get("Config", {}).get("Labels", {}),
}
if compose_file:
success(f"Найден compose-файл: {compose_file}")
else:
warn("Compose-файл не найден автоматически (возможно, запуск через docker run)")
if env_file:
success(f"Найден .env: {env_file}")
return data
def get_container_pid(cid):
"""Возвращает PID контейнера на хосте (для nsenter)"""
try:
out = run(f"docker inspect -f '{{{{.State.Pid}}}}' {cid}", check=False)
return int(out.stdout.strip())
except Exception:
return None