#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/../.." && pwd)}" PROVIDER_MAIN="${ROOT_DIR}/provider/main.go" PROFILE_DIR="${PROFILE_DIR:-}" normalize_api_endpoint() { local endpoint="$1" endpoint="${endpoint%/}" # ⛔ LEGACY: deck-api.ngcloud.ru ЗАКРЫВАЕТСЯ. if [[ -z "$endpoint" ]]; then echo "https://lk-api-gateway.ngcloud.ru/api/v1/svc" return fi # Keep as-is: both legacy proxy (index.cfm) and new REST gateway are valid. # No forced /index.cfm normalization — the downstream tools auto-detect. echo "$endpoint" } if [[ "${1:-}" == "--profile" ]]; then PROFILE_DIR="${2:-}" shift 2 fi if [[ -n "$PROFILE_DIR" ]]; then if [[ ! -d "$PROFILE_DIR" ]]; then echo "Error: profile directory not found: $PROFILE_DIR" >&2 exit 2 fi PROFILE_ENV_FILE="${PROFILE_DIR}/profile.env" if [[ -f "$PROFILE_ENV_FILE" ]]; then set -a # shellcheck disable=SC1090 source "$PROFILE_ENV_FILE" set +a fi REGISTRY_ENV_FILE="${ROOT_DIR}/TOOLS/config/registry.env" if [[ -f "$REGISTRY_ENV_FILE" ]]; then set -a # shellcheck disable=SC1090 source "$REGISTRY_ENV_FILE" set +a fi fi if [[ -z "$PROFILE_DIR" ]]; then echo "Error: --profile is required" >&2 exit 2 fi for required_var in VERSION NAMESPACE PROVIDER_NAME NUBES_API_ENDPOINT REGISTRY_HOSTNAME; do if [[ -z "${!required_var:-}" ]]; then echo "Error: $required_var is required in profile.env or registry.env" >&2 exit 2 fi done resolve_root_path() { local path_value="$1" if [[ -z "$path_value" ]]; then echo "" return fi if [[ "$path_value" = /* ]]; then echo "$path_value" return fi echo "${ROOT_DIR}/${path_value}" } VERSION_ARG="${1:-}" if [[ -n "$VERSION_ARG" ]]; then VERSION="$VERSION_ARG" fi if [[ -z "$VERSION" ]]; then VERSION=$(grep -E 'version string' "$PROVIDER_MAIN" | sed -E 's/.*"([0-9.]+)".*/\1/') fi if [[ -z "$VERSION" ]]; then echo "Error: unable to detect version from $PROVIDER_MAIN" >&2 exit 2 fi REGISTRY_HOST="${REGISTRY_HOST:-tf-docs.nodejsk8s.dev.nubes.ru}" export REGISTRY_HOST NAMESPACE PROVIDER_NAME VERSION S3CFG_REGISTRY="${S3CFG_REGISTRY:-${ROOT_DIR}/secrets/.s3cfg_registry}" S3CFG_REGISTRY="$(resolve_root_path "$S3CFG_REGISTRY")" TMP_DOCS_DIR="" if [[ -n "$PROFILE_DIR" ]]; then # ⛔ NEVER merge with docs/ — ONLY generated docs from generated//docs/ DOCS_GEN_DIR="${DOCS_GEN_DIR:-generated/$(basename "$PROFILE_DIR")/docs}" DOCS_GEN_DIR="$(resolve_root_path "$DOCS_GEN_DIR")" if [[ -d "$DOCS_GEN_DIR" ]]; then export MKDOCS_DOCS_DIR="$DOCS_GEN_DIR" else echo "Error: DOCS_GEN_DIR not found: $DOCS_GEN_DIR" >&2 exit 2 fi fi DOCS_API_ENDPOINT="$NUBES_API_ENDPOINT" DOCS_API_ENDPOINT="$(normalize_api_endpoint "$DOCS_API_ENDPOINT")" DASHBOARD_HOST="deck" if [[ "$NAMESPACE" != "nubes" ]]; then DASHBOARD_HOST="deck-${NAMESPACE#nubes-}" fi DASHBOARD_URL="https://${DASHBOARD_HOST}.ngcloud.ru" PROVIDER_SOURCE="${REGISTRY_HOSTNAME}/${NAMESPACE}/${PROVIDER_NAME}" export DOCS_API_ENDPOINT DASHBOARD_URL PROVIDER_SOURCE load_s3cfg_registry() { local cfg="$1" if [[ ! -f "$cfg" ]]; then echo "Error: S3 config not found: $cfg" >&2 exit 2 fi local access_key secret_key host_base use_https endpoint access_key=$(awk -F '=' '/^\s*access_key\s*=/ {gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2; exit}' "$cfg") secret_key=$(awk -F '=' '/^\s*secret_key\s*=/ {gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2; exit}' "$cfg") host_base=$(awk -F '=' '/^\s*host_base\s*=/ {gsub(/^[ \t]+|[ \t]+$/, "", $2); print $2; exit}' "$cfg") use_https=$(awk -F '=' '/^\s*use_https\s*=/ {gsub(/^[ \t]+|[ \t]+$/, "", $2); print tolower($2); exit}' "$cfg") if [[ -z "${S3_ACCESS_KEY:-}" && -n "$access_key" ]]; then export S3_ACCESS_KEY="$access_key" fi if [[ -z "${S3_SECRET_KEY:-}" && -n "$secret_key" ]]; then export S3_SECRET_KEY="$secret_key" fi if [[ -z "${S3_ENDPOINT:-}" && -n "$host_base" ]]; then if [[ "$host_base" == http* ]]; then endpoint="$host_base" else if [[ "$use_https" == "false" || "$use_https" == "0" || "$use_https" == "no" ]]; then endpoint="http://${host_base}" else endpoint="https://${host_base}" fi fi export S3_ENDPOINT="$endpoint" fi if [[ -z "${S3_ENDPOINT:-}" || -z "${S3_ACCESS_KEY:-}" || -z "${S3_SECRET_KEY:-}" ]]; then echo "Error: S3 credentials are not set (S3_ENDPOINT/S3_ACCESS_KEY/S3_SECRET_KEY)" >&2 exit 2 fi } TMP_CFG="${ROOT_DIR}/.mkdocs.tmp.yml" export ROOT_DIR # Copy 30_registry assets and curated content into docs dir (ДО подстановки версии) if [[ -n "${MKDOCS_DOCS_DIR:-}" ]]; then if [[ -d "${ROOT_DIR}/docs/30_registry" ]]; then mkdir -p "${MKDOCS_DOCS_DIR}/30_registry" cp -r "${ROOT_DIR}/docs/30_registry/"* "${MKDOCS_DOCS_DIR}/30_registry/" 2>/dev/null || true fi if [[ -d "${ROOT_DIR}/docs/curated" ]]; then mkdir -p "${MKDOCS_DOCS_DIR}/curated" cp -r "${ROOT_DIR}/docs/curated/"* "${MKDOCS_DOCS_DIR}/curated/" 2>/dev/null || true fi fi # Per-стенд подстановка во все скопированные Markdown-файлы. if [[ -n "${MKDOCS_DOCS_DIR:-}" ]]; then export DOCS_SUBSTITUTION_ROOT="$MKDOCS_DOCS_DIR" python3 - <<'PY' import os import re from pathlib import Path root = Path(os.environ["DOCS_SUBSTITUTION_ROOT"]) values = { "{{NAMESPACE}}": os.environ["NAMESPACE"], "{{VERSION}}": os.environ["VERSION"], "{{NUBES_API_ENDPOINT}}": os.environ["DOCS_API_ENDPOINT"], "{{DASHBOARD_URL}}": os.environ["DASHBOARD_URL"], "{{PROVIDER_SOURCE}}": os.environ["PROVIDER_SOURCE"], "{{REGISTRY_HOST}}": os.environ["REGISTRY_HOSTNAME"], } legacy_provider_source = re.compile( r'(source\s*=\s*")registry\.kube5s\.ru/[^"\n]+(")' ) for path in root.rglob("*.md"): text = path.read_text(encoding="utf-8") for placeholder, value in values.items(): text = text.replace(placeholder, value) text = legacy_provider_source.sub( lambda match: f'{match.group(1)}{os.environ["PROVIDER_SOURCE"]}{match.group(2)}', text, ) path.write_text(text, encoding="utf-8") for path in root.rglob("*.md"): text = path.read_text(encoding="utf-8") if "{{" in text or "}}" in text: raise SystemExit(f"unresolved documentation placeholder: {path}") PY foreign_namespace="" foreign_api="" foreign_dashboard="" case "$NAMESPACE" in nubes) foreign_namespace="nubes-dev|nubes-test"; foreign_api="lk-api-gateway-(dev|test)"; foreign_dashboard="deck-(dev|test)" ;; nubes-dev) foreign_namespace="nubes-test"; foreign_api="lk-api-gateway\.ngcloud\.ru|lk-api-gateway-test\.ngcloud\.ru"; foreign_dashboard="deck\.ngcloud\.ru|deck-test\.ngcloud\.ru" ;; nubes-test) foreign_namespace="nubes-dev"; foreign_api="lk-api-gateway\.ngcloud\.ru|lk-api-gateway-dev\.ngcloud\.ru"; foreign_dashboard="deck\.ngcloud\.ru|deck-dev\.ngcloud\.ru" ;; *) echo "Error: unsupported namespace for documentation validation: $NAMESPACE" >&2; exit 2 ;; esac if grep -RIlE "$foreign_namespace|$foreign_api|$foreign_dashboard|registry\.kube5s\.ru" "$MKDOCS_DOCS_DIR" --include='*.md' 2>/dev/null | while IFS= read -r docs_file; do grep -vE 'https://tf-docs\.nodejsk8s\.dev\.nubes\.ru/(nubes-dev|nubes-test|nubes)/' "$docs_file" | grep -nE "$foreign_namespace|$foreign_api|$foreign_dashboard|registry\.kube5s\.ru" && printf '%s\n' "$docs_file" done >/tmp/docs-stand-contamination.txt; then echo "Error: stand-specific contamination detected in generated docs:" >&2 cat /tmp/docs-stand-contamination.txt >&2 exit 2 fi fi python3 - <<'PY' import os import re import yaml from pathlib import Path registry_host = os.environ["REGISTRY_HOST"] namespace = os.environ["NAMESPACE"] name = os.environ["PROVIDER_NAME"] version = os.environ["VERSION"] root_dir = Path(os.environ["ROOT_DIR"]) cfg_path = root_dir / "mkdocs.yml" text = cfg_path.read_text(encoding="utf-8") text = re.sub( r"^site_url:\s*.*$", f"site_url: https://{registry_host}/{namespace}/", text, flags=re.M, ) docs_dir = os.environ.get("MKDOCS_DOCS_DIR", "").strip() if docs_dir: # Относительный docs_dir: работает и на хосте (сборка из ROOT_DIR), и в docker (/docs). if os.path.isabs(docs_dir): try: rel = os.path.relpath(docs_dir, os.environ.get("ROOT_DIR", os.path.sep)) if not rel.startswith(".."): docs_dir = rel except ValueError: pass if re.search(r"^docs_dir:\s*.*$", text, flags=re.M): text = re.sub(r"^docs_dir:\s*.*$", f"docs_dir: {docs_dir}", text, flags=re.M) else: text += f"\ndocs_dir: {docs_dir}\n" # Inject resources_nav from _nav_fragment.yml into nav section docs_dir_path = Path(docs_dir) if docs_dir else None nav_fragment_path = docs_dir_path / "_nav_fragment.yml" if docs_dir_path else None if nav_fragment_path and nav_fragment_path.exists(): config = yaml.safe_load(text) fragment = yaml.safe_load(nav_fragment_path.read_text(encoding="utf-8")) resources_nav = fragment.get("resources_nav", []) if resources_nav: nav = config.get("nav", []) for i, item in enumerate(nav): if isinstance(item, dict) and "Ресурсы" in item: # Replace "Ресурсы" children with generated categories item["Ресурсы"] = resources_nav break config["nav"] = nav text = yaml.dump(config, allow_unicode=True, default_flow_style=False, sort_keys=False) (root_dir / ".mkdocs.tmp.yml").write_text(text, encoding="utf-8") PY build_docs() { local docs_build_mode docs_build_mode="${DOCS_BUILD_MODE:-auto}" if [[ -n "${MKDOCS_DOCS_DIR:-}" && "$docs_build_mode" == "auto" ]]; then docs_build_mode="local" fi if [[ "$docs_build_mode" != "local" ]] && command -v docker >/dev/null 2>&1; then local docker_err docker_err="$(mktemp)" if docker run --rm -v "${ROOT_DIR}:/docs" squidfunk/mkdocs-material build -f "/docs/.mkdocs.tmp.yml" -d site 2>"$docker_err"; then rm -f "$docker_err" return 0 fi if grep -q "error while creating mount source path" "$docker_err"; then echo "Warning: docker mount issue detected, using local mkdocs" >&2 else cat "$docker_err" >&2 echo "Warning: docker build failed, trying local mkdocs" >&2 fi rm -f "$docker_err" fi local venv_python venv_python="${ROOT_DIR}/.venv/bin/python" if [[ -x "$venv_python" ]]; then if "$venv_python" -m mkdocs --version >/dev/null 2>&1; then (cd "$ROOT_DIR" && "$venv_python" -m mkdocs build -f "$TMP_CFG" -d site) return 0 fi echo "Warning: mkdocs module is missing in .venv, installing pinned versions" >&2 "$venv_python" -m pip install --disable-pip-version-check -q "mkdocs==1.6.1" "mkdocs-material==9.7.3" (cd "$ROOT_DIR" && "$venv_python" -m mkdocs build -f "$TMP_CFG" -d site) return 0 fi if command -v mkdocs >/dev/null 2>&1 && mkdocs --version >/dev/null 2>&1; then (cd "$ROOT_DIR" && mkdocs build -f "$TMP_CFG" -d site) return 0 fi echo "Error: mkdocs is not installed or not runnable." >&2 echo "Install once: python3 -m venv .venv && .venv/bin/python -m pip install mkdocs==1.6.1 mkdocs-material==9.7.3" >&2 exit 2 } trap 'rm -f "$TMP_CFG"' EXIT build_docs : : load_s3cfg_registry "$S3CFG_REGISTRY" (cd "$ROOT_DIR" && ./scripts/publish-docs.sh site "$REGISTRY_HOST" "$NAMESPACE" "$PROVIDER_NAME" "$VERSION") echo "Docs published for ${VERSION}"