iac/docs/architecture/scan_contour.py

93 lines
4.9 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.

"""Строит матрицу «приложение -> используемый технологический сервис» для контура.
py scan_contour.py <имя-кластера> <выход.json>
Имя кластера — директория в clusters/, например любая из перечисленных
в корневом CLAUDE.md. Сканируются только *.yaml в apps/<app>/base и
apps/<app>/<кластер>; документация (*.md) игнорируется, иначе прозаические
упоминания переменных дают ложные срабатывания.
"""
import re, pathlib, json, sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
if len(sys.argv) != 3:
sys.exit(__doc__)
CLUSTER, OUT = sys.argv[1], pathlib.Path(sys.argv[2])
ROOT = pathlib.Path(__file__).resolve().parents[2]
# Считаем развёрнутыми все приложения репозитория. Имя кластера определяет,
# какой оверлей читать поверх base, но состав приложений им не ограничивается:
# часть сервисов работает в контуре, не будучи заявленной в kustomization.
apps = sorted(d.name for d in (ROOT / "apps").iterdir() if d.is_dir())
with_overlay = [a for a in apps if (ROOT / "apps" / a / CLUSTER).is_dir()]
print(f"приложений: {len(apps)}, из них с оверлеем {CLUSTER}: {len(with_overlay)} "
f"(остальные читаются только из base)\n")
MARKERS = {
"postgres": [r"POSTGRES_[A-Z]", r"DB__HOST", r"postgresql\.postgresql\.svc",
r"secrets/data/apps/[a-z0-9-]+/postgres"],
"rabbitmq": [r"RABBITMQ", r"AMQP__", r"rabbitmq\.rabbitmq\.svc"],
"kafka": [r"KAFKA", r"kafka\.kafka\.svc", r"BOOTSTRAP_SERVERS"],
"camunda": [r"ZEEBE", r"CAMUNDA"],
"s3": [r"S3__", r"S3_ENDPOINT", r"S3_ACCESS", r"S3_BUCKET"],
"redis": [r"REDIS_HOST", r"REDIS__", r"redis\.[a-z-]+\.svc"],
"vault": [r"vault\.hashicorp\.com/agent-inject"],
"oidc": [r"ZITADEL", r"JWKS", r"OIDC", r"jwt-public"],
}
def read(paths):
return "\n".join(p.read_text(encoding="utf-8", errors="replace") for p in paths)
def scan(app):
"""Возвращает по сервису источник связи: manifest, doc или False.
Манифесты — основной источник, но часть переменных приходит из k8s-секретов
(блоки secretEnvs) и в YAML не видна. Поэтому CONFIGURATION.md / ENDPOINTS.md
рядом с манифестами используются как второй источник: связь реальна, просто
не выражена в конфиге.
"""
yaml_blob = read([f for sub in ("base", CLUSTER)
if (ROOT / "apps" / app / sub).is_dir()
for f in (ROOT / "apps" / app / sub).rglob("*.yaml")])
doc_blob = read(sorted((ROOT / "apps" / app).glob("*.md")))
out = {}
for svc, pats in MARKERS.items():
if any(re.search(p, yaml_blob) for p in pats):
out[svc] = "manifest"
elif any(re.search(p, doc_blob, re.I) for p in pats):
out[svc] = "doc"
else:
out[svc] = False
return out
matrix = {a: scan(a) for a in apps}
istio_path = ROOT / "infrastructure" / "istio-config" / CLUSTER / "istio-config.yaml"
exposed_ns = []
if istio_path.is_file():
raw = istio_path.read_text(encoding="utf-8")
body = "\n".join(l for l in raw.splitlines() if not l.lstrip().startswith("#"))
exposed_ns = sorted({ns for _, ns in re.findall(r"service:\s*([a-z0-9-]+)\.([a-z0-9-]+)\.svc", body)})
hdr = ["app"] + list(MARKERS) + ["istio"]
print(f"приложений: {len(apps)}\n")
print(" | ".join(h.ljust(9) for h in hdr))
print("-" * 112)
MARK = {"manifest": "+", "doc": "d", False: ""}
for app, row in matrix.items():
cells = [app.ljust(17)] + [MARK[row[s]].ljust(9) for s in MARKERS]
cells.append("+".ljust(9) if app in exposed_ns else "".ljust(9))
print(" | ".join(cells))
print("\n + связь видна в манифестах, d — только в документации (значение из секрета)")
print("\nпотребителей на сервис:")
for s in MARKERS:
m = sum(1 for r in matrix.values() if r[s] == "manifest")
d = sum(1 for r in matrix.values() if r[s] == "doc")
print(f" {s:10s} {m + d:2d}" + (f" (из них {d} только по документации)" if d else ""))
print(f" {'istio':10s} {len([a for a in apps if a in exposed_ns]):2d}")
OUT.write_text(json.dumps({"apps": apps, "matrix": matrix, "exposed": exposed_ns},
ensure_ascii=False, indent=2), encoding="utf-8")
print(f"\nзаписано: {OUT}")