init commit

This commit is contained in:
2025-11-30 00:12:48 +03:00
commit 3c8b011ed4
7 changed files with 201 additions and 0 deletions
+162
View File
@@ -0,0 +1,162 @@
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", "", "<none>"):
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()