#!/usr/bin/env bash # 2026-03-11 # run_e2e_tests.sh — E2E тест примеров: apply → modify → apply → destroy # # Запускает два примера: hello-node (nodejs20) и simple-python (python3.11) # Каждый проходит полный цикл: init → apply → modify → apply → destroy # В конце кластер должен быть полностью чистым. # # Использование: # ./run_e2e_tests.sh # ./run_e2e_tests.sh hello-node # только один пример # # Требования: terraform, curl в PATH set -uo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" EXAMPLES_DIR="$REPO_ROOT/examples" LOGS_DIR="$REPO_ROOT/.e2e-logs" mkdir -p "$LOGS_DIR" # ── Примеры для запуска ────────────────────────────────────────────────────── if [ $# -gt 0 ]; then EXAMPLES=("$@") else EXAMPLES=("hello-node" "simple-python") fi # ── Цвета ──────────────────────────────────────────────────────────────────── GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; RESET='\033[0m' ok() { echo -e "${GREEN} ✓ $*${RESET}"; } fail() { echo -e "${RED} ✗ $*${RESET}"; } info() { echo -e "${YELLOW} → $*${RESET}"; } # ── Результаты ─────────────────────────────────────────────────────────────── declare -A RESULTS=() # ── Cleanup-trap: уничтожить всё что могло остаться ────────────────────────── cleanup() { for example in "${EXAMPLES[@]}"; do local dir="$EXAMPLES_DIR/$example" if [ -f "$dir/terraform.tfstate" ] && \ grep -q '"resources"' "$dir/terraform.tfstate" 2>/dev/null && \ python3 -c "import json,sys; d=json.load(open('$dir/terraform.tfstate')); sys.exit(0 if d.get('resources') else 1)" 2>/dev/null; then info "cleanup trap: destroying $example" (cd "$dir" && terraform destroy -auto-approve -input=false -no-color \ >> "$LOGS_DIR/${example}-destroy-emergency.log" 2>&1) || true fi restore_backup "$example" done } trap cleanup EXIT # ── Retry wrapper: 3 попытки, ретрай при TLS/EOF ошибках ───────────────────── # Использование: tf_retry tf_retry() { local logfile="$1"; shift local attempt=1 while [ "$attempt" -le 3 ]; do if "$@" 2>&1 | tee "$logfile"; then return 0 fi if grep -Eiq \ 'TLS handshake timeout|unexpected EOF|i/o timeout|context deadline' \ "$logfile" && [ "$attempt" -lt 3 ]; then info "network error, retry $((attempt+1))/3..." attempt=$((attempt + 1)) sleep 3 continue fi return 1 done return 1 } # ── Modify: сохранить бэкап и внести изменение ─────────────────────────────── # Изменения минимальны и легко проверяемы в plan output backup_and_modify() { local example="$1" case "$example" in hello-node) cp "$EXAMPLES_DIR/$example/http.tf" \ "$EXAMPLES_DIR/$example/http.tf.e2e.bak" # memory_mb 128 → 256, добавить env_vars perl -0pi -e \ 's/(memory_mb\s+=\s+)128/${1}256/' \ "$EXAMPLES_DIR/$example/http.tf" perl -0pi -e \ 's/(source_dir\s+=.*\n)/$1\n env_vars = \{ GREETING = "e2e-test" \}\n/' \ "$EXAMPLES_DIR/$example/http.tf" ;; simple-python) cp "$EXAMPLES_DIR/$example/time-display.tf" \ "$EXAMPLES_DIR/$example/time-display.tf.e2e.bak" # memory_mb 64 → 96 perl -0pi -e \ 's/(memory_mb\s+=\s+)64/${1}96/' \ "$EXAMPLES_DIR/$example/time-display.tf" ;; simple-node) cp "$EXAMPLES_DIR/$example/time-display.tf" \ "$EXAMPLES_DIR/$example/time-display.tf.e2e.bak" perl -0pi -e \ 's/(memory_mb\s+=\s+)64/${1}96/' \ "$EXAMPLES_DIR/$example/time-display.tf" ;; esac } restore_backup() { local example="$1" for bak in "$EXAMPLES_DIR/$example"/*.e2e.bak; do [ -f "$bak" ] || continue mv "$bak" "${bak%.e2e.bak}" done } # ── Шаг apply: проверяем что terraform вернул 0 и есть "Apply complete" ─────── assert_apply_ok() { local logfile="$1" if ! grep -q 'Apply complete' "$logfile"; then return 1 fi # Выводим краткий итог grep -E 'Apply complete|added|changed|destroyed|Outputs:' "$logfile" | head -5 return 0 } assert_destroy_ok() { local logfile="$1" grep -q 'Destroy complete' "$logfile" } # ── Запуск одного примера ───────────────────────────────────────────────────── run_example() { local example="$1" local dir="$EXAMPLES_DIR/$example" local log_base="$LOGS_DIR/$example" local failed=0 echo echo "════════════════════════════════════════" echo " EXAMPLE: $example" echo "════════════════════════════════════════" # Чистим локальные артефакты от предыдущих запусков rm -rf "$dir/.terraform" "$dir/.terraform.lock.hcl" \ "$dir/terraform.tfstate" "$dir/terraform.tfstate.backup" \ "$dir"/terraform.tfstate.*.backup # 1. init ─────────────────────────────────────────────────────────────────── info "init" if tf_retry "${log_base}-init.log" \ terraform -chdir="$dir" init -input=false -no-color; then ok "init" else fail "init — см. ${log_base}-init.log" RESULTS[$example]="FAIL (init)" return 1 fi # 2. apply ────────────────────────────────────────────────────────────────── info "apply" if tf_retry "${log_base}-apply.log" \ terraform -chdir="$dir" apply -auto-approve -input=false -no-color \ && assert_apply_ok "${log_base}-apply.log"; then ok "apply" else fail "apply — см. ${log_base}-apply.log" RESULTS[$example]="FAIL (apply)" return 1 fi # 3. modify ───────────────────────────────────────────────────────────────── info "modify (backup + patch)" backup_and_modify "$example" ok "files patched" # 4. apply after modify ───────────────────────────────────────────────────── info "apply (после modify)" if tf_retry "${log_base}-apply-modify.log" \ terraform -chdir="$dir" apply -auto-approve -input=false -no-color \ && assert_apply_ok "${log_base}-apply-modify.log"; then ok "apply (modify)" # Показываем что изменилось grep -E 'changed|updated|Modifying' "${log_base}-apply-modify.log" | head -3 \ || true else fail "apply (modify) — см. ${log_base}-apply-modify.log" RESULTS[$example]="FAIL (apply-modify)" failed=1 fi # Восстанавливаем файлы до destroy restore_backup "$example" # 5. destroy ──────────────────────────────────────────────────────────────── info "destroy" if tf_retry "${log_base}-destroy.log" \ terraform -chdir="$dir" destroy -auto-approve -input=false -no-color \ && assert_destroy_ok "${log_base}-destroy.log"; then ok "destroy" grep 'Destroy complete' "${log_base}-destroy.log" || true else fail "destroy — см. ${log_base}-destroy.log" RESULTS[$example]="FAIL (destroy)" return 1 fi # Чистим после успешного прогона rm -rf "$dir/.terraform" "$dir/.terraform.lock.hcl" \ "$dir/terraform.tfstate" "$dir/terraform.tfstate.backup" if [ "$failed" -eq 0 ]; then RESULTS[$example]="PASS" fi } # ── Main ────────────────────────────────────────────────────────────────────── main() { echo echo "╔══════════════════════════════════════╗" echo "║ sless E2E tests $(date '+%Y-%m-%d %H:%M') ║" echo "╚══════════════════════════════════════╝" echo "Примеры: ${EXAMPLES[*]}" echo "Логи: $LOGS_DIR" for example in "${EXAMPLES[@]}"; do run_example "$example" || true done # ── Итог ──────────────────────────────────────────────────────────────────── echo echo "════════════════ ИТОГ ════════════════" local all_pass=1 for example in "${EXAMPLES[@]}"; do local result="${RESULTS[$example]:-UNKNOWN}" if [ "$result" = "PASS" ]; then ok "$example: $result" else fail "$example: $result" all_pass=0 fi done echo "══════════════════════════════════════" if [ "$all_pass" -eq 1 ]; then echo -e "${GREEN}Все тесты прошли успешно.${RESET}" exit 0 else echo -e "${RED}Есть ошибки. Логи: $LOGS_DIR${RESET}" exit 1 fi } main