Files
tf_provider/TOOLS/scripts/01_generate_yamls.sh
T
Repinoid 1e796c8eb1 chore(tools): удалить мёртвые легаси-скрипты и общий services_list.txt
Удалено (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)

Все ссылки в документации указывали на профильные списки либо будут
исправлены отдельным коммитом.
2026-09-30 09:50:28 +03:00

273 lines
11 KiB
Bash
Executable File
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.
#!/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}"