import docker import re from typing import List, Tuple, Optional, Dict SAFE_VERSIONS: Dict[str, str] = { #Потом тут будет взятие версий и CVE из базы CVE "nginx": "1.25.4", "redis": "7.2.4", "postgres": "16.2", "mysql": "8.0.36", } def parse_image_ref(image_ref: str) -> Tuple[str, str]: if ":" in image_ref: repo, tag = image_ref.rsplit(":", 1) else: repo, tag = image_ref, "latest" name = repo.split("/")[-1] return name, tag def extract_version_from_tag(tag: str) -> Optional[str]: tag = tag.strip() if tag in ("latest", "", ""): return None base = re.split(r"[^0-9.]", tag)[0] if not base or not re.search(r"\d", base): return None return base def version_tuple(v: str) -> Tuple[int, ...]: parts = v.split(".") nums: List[int] = [] for p in parts: if p.isdigit(): nums.append(int(p)) else: # На всякий случай вытащим ведущие цифры m = re.match(r"(\d+)", p) if m: nums.append(int(m.group(1))) return tuple(nums) def compare_versions(v1: str, v2: str) -> int: t1 = version_tuple(v1) t2 = version_tuple(v2) max_len = max(len(t1), len(t2)) t1 += (0,) * (max_len - len(t1)) t2 += (0,) * (max_len - len(t2)) if t1 < t2: return -1 if t1 > t2: return 1 return 0 def detect_service(name: str) -> Optional[str]: lname = name.lower() for service in SAFE_VERSIONS.keys(): if service in lname: return service return None def main() -> None: client = docker.from_env() containers = client.containers.list(all=True) rows = [] for c in containers: image_tags = c.image.tags if image_tags: image_ref = image_tags[0] else: image_ref = c.attrs["Config"]["Image"] service_name_raw, tag = parse_image_ref(image_ref) service = detect_service(service_name_raw) version = extract_version_from_tag(tag) status = "UNKNOWN" note = "" if service is None: status = "SKIP" note = "Неизвестный сервис (не в списке SAFE_VERSIONS)" elif version is None: status = "UNKNOWN" note = "Не удалось определить версию из тега" else: safe_ver = SAFE_VERSIONS.get(service) if safe_ver is None: status = "UNKNOWN" note = "Нет эталонной версии для сравнения" else: cmp_res = compare_versions(version, safe_ver) if cmp_res < 0: status = "OUTDATED" note = f"Версия ниже безопасной ({safe_ver})" elif cmp_res == 0: status = "SAFE" note = "Соответствует безопасной версии" else: status = "NEWER" note = f"Версия выше эталонной ({safe_ver})" rows.append( { "container": c.name, "image": image_ref, "service": service or "-", "version": version or "-", "status": status, "note": note, } ) headers = ["CONTAINER", "IMAGE", "SERVICE", "VERSION", "STATUS", "NOTE"] col_widths = {h: len(h) for h in headers} for r in rows: col_widths["CONTAINER"] = max(col_widths["CONTAINER"], len(r["container"])) col_widths["IMAGE"] = max(col_widths["IMAGE"], len(r["image"])) col_widths["SERVICE"] = max(col_widths["SERVICE"], len(str(r["service"]))) col_widths["VERSION"] = max(col_widths["VERSION"], len(str(r["version"]))) col_widths["STATUS"] = max(col_widths["STATUS"], len(r["status"])) col_widths["NOTE"] = max(col_widths["NOTE"], len(r["note"])) header_line = " ".join(h.ljust(col_widths[h]) for h in headers) sep_line = " ".join("-" * col_widths[h] for h in headers) print(header_line) print(sep_line) for r in rows: line = " ".join( [ r["container"].ljust(col_widths["CONTAINER"]), r["image"].ljust(col_widths["IMAGE"]), str(r["service"]).ljust(col_widths["SERVICE"]), str(r["version"]).ljust(col_widths["VERSION"]), r["status"].ljust(col_widths["STATUS"]), r["note"].ljust(col_widths["NOTE"]), ] ) print(line) total = len(rows) outdated = sum(1 for r in rows if r["status"] == "OUTDATED") safe = sum(1 for r in rows if r["status"] == "SAFE") unknown = sum(1 for r in rows if r["status"] == "UNKNOWN") print() print(f"Всего контейнеров: {total}") print(f"Безопасных: {safe}") print(f"Устаревших: {outdated}") print(f"Неопределённых: {unknown}") if __name__ == "__main__": main()