iac/docs/architecture/scan_contour.py

90 lines
4.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.

"""Строит матрицу «приложение -> используемый технологический сервис» для контура.
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]
kust = (ROOT / "clusters" / CLUSTER / "kustomization.yaml").read_text(encoding="utf-8")
apps = sorted(set(re.findall(r"\.\./\.\./apps/([a-z0-9-]+)/" + re.escape(CLUSTER), kust)))
if not apps:
sys.exit(f"в clusters/{CLUSTER}/kustomization.yaml не найдено ни одного приложения")
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}")