Документация и kubectl из контейнера; Kind Clusters Dashboard

- Цель make docker|podman kubectl CLUSTER=… (KUBECTL_ARGS) — exec kubectl в kind-k8s-web
- README: без kubectl на хосте; раздел про проверку API из контейнера
- create_cluster/cluster_status: подсказки для UI, make kubectl и exec в контейнере
- app/docs: api_routes.md и README.md про kubectl и API workloads
- Прочее: переименование проекта, документация, UI документации (ранее в рабочем дереве)
This commit is contained in:
Sergey Antropoff
2026-04-04 06:27:18 +03:00
parent c1e867a01f
commit d1a01cca9e
17 changed files with 483 additions and 131 deletions
+44 -3
View File
@@ -1,10 +1,11 @@
"""Чтение README.md для API ``GET /api/v1/docs/readme`` и страницы «Документация».
"""Чтение README.md и безопасное чтение ``app/docs/*.md`` для API документации.
Разметка Markdown преобразуется в браузере: ``/static/js/vendor/marked.min.js`` и
``purify.min.js`` (файлы входят в репозиторий, без CDN).
Путь к файлу: ``KIND_K8S_README_PATH`` или ``README.md`` в корне рядом с ``app/``;
в Docker-образе — ``/opt/kind-k8s/README.md``.
README: ``KIND_K8S_README_PATH`` или ``README.md`` в корне рядом с ``app/``;
в Docker-образе — ``/opt/kind-k8s/README.md``. Файлы под ``app/docs/`` — через
``resolve_app_docs_markdown`` / ``read_app_docs_file_text`` (эндпоинт ``GET /api/v1/docs/file``).
Автор: Сергей Антропов
Сайт: https://devops.org.ru
@@ -86,3 +87,43 @@ def read_readme_text() -> str:
[str(x) for x in _candidates_without_env()],
)
raise FileNotFoundError("README.md")
def repo_root() -> Path:
"""Корень репозитория (родитель каталога ``app/``)."""
return _LIB_FILE.parents[2]
def resolve_app_docs_markdown(relative_path: str) -> Path | None:
"""
Безопасно разрешить путь к ``.md`` только внутри ``app/docs/``.
Ожидается вид ``app/docs/api_routes.md`` (без ``..`` и абсолютных путей).
"""
raw = (relative_path or "").strip().lstrip("/").replace("\\", "/")
if not raw or ".." in raw or "\x00" in raw:
return None
if not raw.endswith(".md"):
return None
if not raw.startswith("app/docs/"):
return None
base = repo_root()
try:
full = (base / raw).resolve()
allowed = (base / "app" / "docs").resolve()
full.relative_to(allowed)
except (ValueError, OSError):
return None
if not full.is_file():
return None
return full
def read_app_docs_file_text(relative_path: str) -> str:
"""Прочитать UTF-8 текст файла под ``app/docs/``; ``FileNotFoundError`` если путь недопустим или файла нет."""
p = resolve_app_docs_markdown(relative_path)
if p is None:
raise FileNotFoundError(relative_path)
text = p.read_text(encoding="utf-8")
logger.debug("Документ app/docs: %s, %s символов", p, len(text))
return text