Удалено (100% мёртвое, ничего не вызывает): - TOOLS/scripts/10_yaml_stability_run.sh - TOOLS/scripts/11_yaml_stability_run_latest.sh - TOOLS/scripts/12_generate_yamls_latest.sh - TOOLS/scripts/13_generate_yamls_clean.sh - TOOLS/scripts/02_generate_resources_and_docs_template_v2.sh - TOOLS/config/services_list.txt (общий список: код его не читает, а как «объединение» он устарел — в нём активен id 27, который в test/prod закомментирован как «нет в UI», и отсутствуют 87/88/97/153 из dev) Все ссылки в документации указывали на профильные списки либо будут исправлены отдельным коммитом.
273 lines
11 KiB
Bash
Executable File
273 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
ROOT_DIR="${ROOT_DIR:-$(cd "${SCRIPT_DIR}/../.." && pwd)}"
|
||
PROVIDER_DIR="${ROOT_DIR}/provider"
|
||
PROFILE_DIR="${PROFILE_DIR:-}"
|
||
|
||
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
|
||
fi
|
||
|
||
if [[ -z "$PROFILE_DIR" ]]; then
|
||
echo "Error: --profile <path> is required. Strict mode stores generated files per stand only." >&2
|
||
exit 2
|
||
fi
|
||
|
||
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}"
|
||
}
|
||
|
||
# Список сервисов — только из профиля стенда (TOOLS/config/<stand>/services_list.txt).
|
||
# Общий TOOLS/config/services_list.txt удалён 2026-09-30 (не использовался ни одним профилем).
|
||
SERVICES_FILE_DEFAULT="${PROFILE_DIR}/services_list.txt"
|
||
SERVICES_FILE="${SERVICES_FILE:-$SERVICES_FILE_DEFAULT}"
|
||
SERVICES_FILE="$(resolve_root_path "$SERVICES_FILE")"
|
||
|
||
# Retry behavior (per service): attempts and delay between attempts.
|
||
REQUEST_DELAY="${REQUEST_DELAY:-0.5}"
|
||
ATTEMPTS="${ATTEMPTS:-3}"
|
||
|
||
# Auth: токен из NUBES_API_TOKEN или из файла TOKEN_FILE (profile.env).
|
||
TOKEN_FILE="${TOKEN_FILE:-}"
|
||
NUBES_API_TOKEN="${NUBES_API_TOKEN:-}"
|
||
if [[ -n "$TOKEN_FILE" ]]; then
|
||
TOKEN_FILE="$(resolve_root_path "$TOKEN_FILE")"
|
||
fi
|
||
|
||
# Назначение:
|
||
# - Берет список сервисов из services_list.txt профиля стенда.
|
||
# - Для каждого сервиса запрашивает единый spec через API и пишет YAML в
|
||
# ${ROOT_DIR}/generated/<stand>/resources_yaml (per-stand, см. ниже).
|
||
# - Формат имени файла: ID_имя.yaml (например, 13_s3bucket.yaml).
|
||
#
|
||
# Безопасность записи (best practice, с 2026-09-30):
|
||
# - Генерация идёт во временный каталог (staging), старый каталог не удаляется заранее.
|
||
# - Замена каталога атомарна и делается ТОЛЬКО при полном успехе; предыдущий
|
||
# каталог уезжает в бэкап resources_yaml.bak-<UTC>, хранятся последние KEEP_BACKUPS.
|
||
# - При любом сбое рабочий каталог остаётся нетронутым, частичный результат — в staging.
|
||
#
|
||
# Источники данных:
|
||
# - Список сервисов: TOOLS/config/{stand}/services_list.txt
|
||
# - API endpoint: ${API_ENDPOINT} (обязателен, только из profile.env)
|
||
# - Токен: переменная NUBES_API_TOKEN или файл TOKEN_FILE (из profile.env).
|
||
#
|
||
# Формат services_list.txt:
|
||
# - Поле 1: service_id (обязательно).
|
||
# - Поле 2: имя сервиса (используется для лога; можно опустить).
|
||
# - Всё после # — заметка для девопса (не используется).
|
||
#
|
||
# Задержки и попытки:
|
||
# - REQUEST_DELAY (секунды) между запросами и ретраями (по умолчанию 0.5).
|
||
# - ATTEMPTS — число попыток на сервис (по умолчанию 3).
|
||
#
|
||
# Логи ошибок:
|
||
# - Список неуспешных сервисов пишется в generated/<stand>/tmp/yaml_gen_failures.txt.
|
||
#
|
||
# Токен: NUBES_API_TOKEN, иначе файл TOKEN_FILE (из profile.env).
|
||
# Легаси-поиск «последнего» *.token в корне репо удален (2026-09-30): токенов там
|
||
# нет, а такой поиск мог молча подхватить чужой токен.
|
||
if [[ -z "$NUBES_API_TOKEN" && -n "$TOKEN_FILE" && -f "$TOKEN_FILE" ]]; then
|
||
NUBES_API_TOKEN=$(cat "$TOKEN_FILE")
|
||
fi
|
||
|
||
if [[ -z "$NUBES_API_TOKEN" ]]; then
|
||
echo "Error: NUBES_API_TOKEN or TOKEN_FILE is required" >&2
|
||
exit 2
|
||
fi
|
||
|
||
if [[ ! -f "$SERVICES_FILE" ]]; then
|
||
echo "Error: services file not found: $SERVICES_FILE" >&2
|
||
exit 2
|
||
fi
|
||
|
||
# Endpoint ОБЯЗАТЕЛЕН: берется ТОЛЬКО из NUBES_API_ENDPOINT (profile.env).
|
||
# Легаси-фолбэк на PROD gateway удален (2026-09-30): молчаливый уход в прод недопустим.
|
||
if [[ -z "${NUBES_API_ENDPOINT:-}" ]]; then
|
||
echo "Error: NUBES_API_ENDPOINT is required (set it in ${PROFILE_DIR}/profile.env)." >&2
|
||
exit 2
|
||
fi
|
||
API_ENDPOINT="$NUBES_API_ENDPOINT"
|
||
# Auto-detect API style: if endpoint contains "index.cfm" → legacy proxy (?endpoint=),
|
||
# otherwise → new REST gateway (direct paths). No forced /index.cfm normalization.
|
||
|
||
STAND="$(basename "$PROFILE_DIR")"
|
||
GENERATED_DIR="${ROOT_DIR}/generated/${STAND}"
|
||
YAML_OUTPUT_DIR_DEFAULT="${GENERATED_DIR}/resources_yaml"
|
||
FAILURES_FILE_DEFAULT="${GENERATED_DIR}/tmp/yaml_gen_failures.txt"
|
||
|
||
YAML_OUTPUT_DIR="${YAML_OUTPUT_DIR:-$YAML_OUTPUT_DIR_DEFAULT}"
|
||
FAILURES_FILE="${FAILURES_FILE:-$FAILURES_FILE_DEFAULT}"
|
||
YAML_OUTPUT_DIR="$(resolve_root_path "$YAML_OUTPUT_DIR")"
|
||
FAILURES_FILE="$(resolve_root_path "$FAILURES_FILE")"
|
||
|
||
YAML_PARENT_DIR="$(dirname "$YAML_OUTPUT_DIR")"
|
||
YAML_BASE_NAME="$(basename "$YAML_OUTPUT_DIR")"
|
||
mkdir -p "$YAML_PARENT_DIR"
|
||
mkdir -p "$(dirname "$FAILURES_FILE")"
|
||
|
||
# Защита от генерации не в тот стенд: каталог помечается маркером .stand.
|
||
if [[ -d "$YAML_OUTPUT_DIR" && -f "${YAML_OUTPUT_DIR}/.stand" ]]; then
|
||
existing_stand="$(cat "${YAML_OUTPUT_DIR}/.stand" 2>/dev/null || true)"
|
||
if [[ -n "$existing_stand" && "$existing_stand" != "$STAND" ]]; then
|
||
echo "Error: ${YAML_OUTPUT_DIR} belongs to stand '${existing_stand}', not '${STAND}'. Refusing." >&2
|
||
exit 2
|
||
fi
|
||
fi
|
||
|
||
# Staging: старый каталог НЕ удаляется заранее, замена — атомарная (mv в конце).
|
||
STAGING_DIR="${YAML_OUTPUT_DIR}.staging.$$"
|
||
rm -rf "$STAGING_DIR"
|
||
mkdir -p "$STAGING_DIR"
|
||
|
||
# embed.go обязателен: пакет resources_yaml используется через go:embed *.yaml.
|
||
cat > "${STAGING_DIR}/embed.go" <<'EOF'
|
||
package resources_yaml
|
||
|
||
import "embed"
|
||
|
||
// Files embeds unified service YAML specs for provider runtime lookup.
|
||
//
|
||
//go:embed *.yaml
|
||
var Files embed.FS
|
||
EOF
|
||
printf '%s\n' "$STAND" > "${STAGING_DIR}/.stand"
|
||
|
||
rm -f "$FAILURES_FILE"
|
||
|
||
# Auto-rebuild yaml-generator if sources are newer than binary
|
||
BIN="${ROOT_DIR}/TOOLS/bin/yaml-generator"
|
||
SRC="${ROOT_DIR}/TOOLS/yaml-generator/"
|
||
if [[ ! -x "$BIN" ]] || find "$SRC" -type f -newer "$BIN" -print -quit | grep -q .; then
|
||
echo "Building yaml-generator..."
|
||
(cd "${ROOT_DIR}/TOOLS/yaml-generator" && go build -o "$BIN" .)
|
||
fi
|
||
|
||
# Проверка что список сервисов не пустой
|
||
svc_count=$(grep -cv '^\s*$\|^\s*#' "$SERVICES_FILE" || true)
|
||
if [[ "$svc_count" -eq 0 ]]; then
|
||
echo "Error: services list is empty or all commented out: $SERVICES_FILE" >&2
|
||
exit 2
|
||
fi
|
||
echo "Generating YAML for ${svc_count} services from ${SERVICES_FILE}"
|
||
|
||
# Читаем список сервисов, пропуская пустые строки и комментарии.
|
||
while IFS= read -r line; do
|
||
line="${line//$'\r'/}"
|
||
if [[ -z "$line" ]]; then
|
||
continue
|
||
fi
|
||
if [[ "$line" == \#* ]]; then
|
||
continue
|
||
fi
|
||
sid=$(echo "$line" | awk '{print $1}')
|
||
# Имя сервиса берётся из 2-го поля services_list.txt. API ради имени не дёргаем:
|
||
# Go-генератор сам запрашивает spec и нормализует имя (это убирает лишний запрос).
|
||
svc_name="$(echo "$line" | awk '{print $2}')"
|
||
if [[ "$svc_name" == \#* ]]; then
|
||
svc_name=""
|
||
fi
|
||
|
||
if [[ -z "$sid" ]]; then
|
||
continue
|
||
fi
|
||
|
||
if [[ ! "$sid" =~ ^[0-9]+$ ]]; then
|
||
echo "Error: invalid service id '${sid}' in ${SERVICES_FILE}" >&2
|
||
echo "${sid} invalid_id" >> "$FAILURES_FILE"
|
||
continue
|
||
fi
|
||
|
||
# Один YAML на сервис формируется Go-генератором — пишем во временный каталог.
|
||
echo "Generating unified spec for ${sid} (${svc_name})"
|
||
success=0
|
||
# Generator normalizes names, so verify output by ID prefix only.
|
||
output_glob="${STAGING_DIR}/${sid}_*.yaml"
|
||
|
||
# Старый YAML НЕ удаляем заранее: полная замена каталога — атомарная, в конце.
|
||
# Генерация YAML для выбранного сервиса с ретраями.
|
||
for attempt in $(seq 1 "$ATTEMPTS"); do
|
||
(
|
||
cd "$PROVIDER_DIR"
|
||
NUBES_API_TOKEN="$NUBES_API_TOKEN" \
|
||
NUBES_SERVICE_ID="$sid" \
|
||
NUBES_SERVICE_NAME="$svc_name" \
|
||
NUBES_API_ENDPOINT="$API_ENDPOINT" \
|
||
NUBES_OUTPUT_DIR="$STAGING_DIR" \
|
||
"${ROOT_DIR}/TOOLS/bin/yaml-generator"
|
||
) && success=1 || success=0
|
||
|
||
if [[ "$success" -eq 1 ]]; then
|
||
# Success means output file exists for the service ID.
|
||
if ! compgen -G "$output_glob" > /dev/null; then
|
||
echo "Error: output not created ${output_glob} for ${sid} (${svc_name})" >&2
|
||
success=0
|
||
fi
|
||
fi
|
||
|
||
if [[ "$success" -eq 1 ]]; then
|
||
break
|
||
fi
|
||
echo "Retry ${attempt}/${ATTEMPTS} failed for ${sid} (${svc_name})" >&2
|
||
sleep "$REQUEST_DELAY"
|
||
done
|
||
|
||
# Фиксируем неудачные сервисы для анализа.
|
||
if [[ "$success" -ne 1 ]]; then
|
||
echo "${sid} ${svc_name}" >> "$FAILURES_FILE"
|
||
fi
|
||
|
||
sleep "$REQUEST_DELAY"
|
||
|
||
done < "$SERVICES_FILE"
|
||
|
||
if [[ -s "$FAILURES_FILE" ]]; then
|
||
echo "Failed services (see $FAILURES_FILE):" >&2
|
||
cat "$FAILURES_FILE" >&2
|
||
echo "" >&2
|
||
echo "Замена каталога ОТМЕНЕНА: рабочий ${YAML_OUTPUT_DIR} не тронут." >&2
|
||
echo "Частичный результат оставлен для разбора: $STAGING_DIR" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# Атомарная замена: старый каталог уезжает в бэкап, новый встаёт на его место.
|
||
BACKUP_DIR="${YAML_PARENT_DIR}/${YAML_BASE_NAME}.bak-$(date -u +%Y%m%dT%H%M%SZ)"
|
||
if [[ -d "$YAML_OUTPUT_DIR" ]]; then
|
||
mv "$YAML_OUTPUT_DIR" "$BACKUP_DIR"
|
||
echo "Backup of previous specs: $BACKUP_DIR"
|
||
fi
|
||
mv "$STAGING_DIR" "$YAML_OUTPUT_DIR"
|
||
|
||
# Ротация бэкапов: держим последние KEEP_BACKUPS (по умолчанию 5).
|
||
KEEP_BACKUPS="${KEEP_BACKUPS:-5}"
|
||
mapfile -t stale_backups < <(ls -1dt "${YAML_PARENT_DIR}/${YAML_BASE_NAME}.bak-"* 2>/dev/null | tail -n +"$((KEEP_BACKUPS + 1))")
|
||
if [[ "${#stale_backups[@]}" -gt 0 ]]; then
|
||
rm -rf -- "${stale_backups[@]}"
|
||
fi
|
||
|
||
echo "Done. YAML files are in ${YAML_OUTPUT_DIR}" |