Example Terraform configuration for testing PostgreSQL integration: - main.tf: VPC and database setup - postgres.tf: Database resource definitions - outputs.tf: Output values for connection - test_basic.sh: Basic connectivity tests - test_lifecycle.sh: Full lifecycle testing - terraform.tfvars.example: Configuration template - .gitignore: Ignore sensitive data and terraform artifacts
188 lines
11 KiB
Bash
188 lines
11 KiB
Bash
#!/usr/bin/env bash
|
||
# 2026-04-01 — test_lifecycle.sh
|
||
# Гоняет реальный API Nubes: создание/удаление/модификация пользователей и БД.
|
||
# Каждый шаг — отдельный terraform apply с живым выводом.
|
||
# Запуск: bash test_lifecycle.sh
|
||
|
||
set -uo pipefail
|
||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
cd "$DIR"
|
||
|
||
GREEN="\033[0;32m"; RED="\033[0;31m"; YELLOW="\033[1;33m"; CYAN="\033[0;36m"; NC="\033[0m"
|
||
PASS=0; FAIL=0
|
||
|
||
ok() { echo -e "\n${GREEN}>>> PASS${NC} $1"; PASS=$((PASS+1)); }
|
||
fail() { echo -e "\n${RED}>>> FAIL${NC} $1"; FAIL=$((FAIL+1)); }
|
||
section() { echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n $1\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; }
|
||
step() { echo -e "\n${CYAN}--- $1 ---${NC}"; }
|
||
|
||
# run_apply — terraform apply с живым выводом в терминал.
|
||
run_apply() {
|
||
echo ""
|
||
terraform apply -auto-approve
|
||
return $?
|
||
}
|
||
|
||
# run_apply_expect_fail — apply должен упасть (ошибка API = успех теста).
|
||
run_apply_expect_fail() {
|
||
local label="$1"
|
||
echo ""
|
||
if terraform apply -auto-approve; then
|
||
fail "$label — ожидали ошибку API, но apply прошёл!"
|
||
else
|
||
ok "$label — API вернул ошибку (ожидаемо)"
|
||
fi
|
||
}
|
||
|
||
echo -e "\n${YELLOW}╔══════════════════════════════════════════════════════╗"
|
||
echo "║ PG_TEST lifecycle — $(date '+%Y-%m-%d %H:%M:%S') ║"
|
||
echo -e "╚══════════════════════════════════════════════════════╝${NC}"
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
section "ШАГ 0 — Очистка: destroy всего перед стартом"
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# Гарантируем чистый старт — убираем все ресурсы и state.
|
||
step "terraform destroy (убираем всё что осталось от предыдущих прогонов)"
|
||
terraform destroy -auto-approve || true # не падаем если уже пусто
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
section "ШАГ 1 — Создать: 2 пользователя + 2 БД + 1 app_user"
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
step "terraform apply (postgres.tf + postgres_extra.tf)"
|
||
if run_apply; then
|
||
ok "Создание прошло"
|
||
else
|
||
fail "Создание упало — дальше не идём"
|
||
exit 1
|
||
fi
|
||
echo ""; echo "Ресурсы в state:"; terraform state list | grep -v pg_test_instance
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
section "ШАГ 2 — Удалить extra_user2 и extra_db2"
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
step "Убираем test_extra_user2 и test_extra_db2 из tf"
|
||
python3 - <<'PYEOF'
|
||
import re, pathlib
|
||
|
||
def comment_block(path, resource_type, resource_name):
|
||
text = pathlib.Path(path).read_text()
|
||
pattern = rf'(resource\s+"{re.escape(resource_type)}"\s+"{re.escape(resource_name)}"\s*\{{)'
|
||
match = re.search(pattern, text)
|
||
if not match:
|
||
print(f" WARNING: {resource_type}.{resource_name} not found"); return
|
||
start = match.start(); depth, i = 0, start
|
||
while i < len(text):
|
||
if text[i] == '{': depth += 1
|
||
elif text[i] == '}':
|
||
depth -= 1
|
||
if depth == 0: end = i + 1; break
|
||
i += 1
|
||
pathlib.Path(path).write_text(
|
||
text[:start] + "/* DISABLED\n" + text[start:end] + "\nDISABLED */" + text[end:]
|
||
)
|
||
print(f" скрыт: {resource_type}.{resource_name}")
|
||
|
||
comment_block("postgres_extra.tf", "nubes_postgres_user", "test_extra_user2")
|
||
comment_block("postgres_extra.tf", "nubes_postgres_database", "test_extra_db2")
|
||
PYEOF
|
||
|
||
step "terraform apply — API удаляет user2 и db2"
|
||
if run_apply; then ok "Удаление прошло"; else fail "Удаление упало"; fi
|
||
echo ""; echo "Ресурсы в state:"; terraform state list | grep -v pg_test_instance
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
section "ШАГ 3 — Воссоздать extra_user2 и extra_db2"
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
step "Восстанавливаем tf"
|
||
python3 - <<'PYEOF'
|
||
import pathlib, re
|
||
p = pathlib.Path("postgres_extra.tf")
|
||
text = re.sub(r'/\* DISABLED\n', '', p.read_text())
|
||
text = re.sub(r'\nDISABLED \*/', '', text)
|
||
p.write_text(text); print(" postgres_extra.tf восстановлен")
|
||
PYEOF
|
||
|
||
step "terraform apply — API воссоздаёт user2 и db2"
|
||
if run_apply; then ok "Воссоздание прошло"; else fail "Воссоздание упало"; fi
|
||
echo ""; echo "Ресурсы в state:"; terraform state list | grep -v pg_test_instance
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
section "ШАГ 4 — Модификация: сменить db_owner у extra_db1"
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
step "db_owner test_extra_db1: extra_user1 → extra_user2"
|
||
python3 - <<'PYEOF'
|
||
import pathlib
|
||
p = pathlib.Path("postgres_extra.tf")
|
||
text = p.read_text().replace(
|
||
'nubes_postgres_user.test_extra_user1.username',
|
||
'nubes_postgres_user.test_extra_user2.username', 1)
|
||
p.write_text(text); print(" db_owner: user1 → user2")
|
||
PYEOF
|
||
|
||
step "terraform apply — API обновляет db_owner"
|
||
if run_apply; then ok "Смена db_owner прошла"; else fail "Смена db_owner упала"; fi
|
||
|
||
step "Откат db_owner обратно (user2 → user1)"
|
||
python3 - <<'PYEOF'
|
||
import pathlib
|
||
p = pathlib.Path("postgres_extra.tf")
|
||
text = p.read_text().replace(
|
||
'nubes_postgres_user.test_extra_user2.username',
|
||
'nubes_postgres_user.test_extra_user1.username', 1)
|
||
p.write_text(text); print(" db_owner: user2 → user1")
|
||
PYEOF
|
||
run_apply > /dev/null 2>&1 || true
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
section "ШАГ 5 — Невалидные параметры: ждём ошибку API"
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
step "Тест 5a: db_owner = несуществующий пользователь"
|
||
cat > ./pg_test_invalid.tf <<'TFEOF'
|
||
resource "nubes_postgres_database" "test_invalid_owner" {
|
||
postgres_id = nubes_postgres.pg_test_instance.id
|
||
db_name = "invalid_owner_db"
|
||
db_owner = "this_user_does_not_exist"
|
||
adopt_existing_on_create = false
|
||
}
|
||
TFEOF
|
||
run_apply_expect_fail "5a: db_owner='this_user_does_not_exist'"
|
||
rm -f ./pg_test_invalid.tf; run_apply > /dev/null 2>&1 || true
|
||
|
||
step "Тест 5b: role = несуществующая строка"
|
||
cat > ./pg_test_invalid.tf <<'TFEOF'
|
||
resource "nubes_postgres_user" "test_invalid_role" {
|
||
postgres_id = nubes_postgres.pg_test_instance.id
|
||
username = "invalid_role_user"
|
||
role = "fantasy_role_xyz"
|
||
adopt_existing_on_create = false
|
||
}
|
||
TFEOF
|
||
run_apply_expect_fail "5b: role='fantasy_role_xyz'"
|
||
rm -f ./pg_test_invalid.tf; run_apply > /dev/null 2>&1 || true
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
section "ШАГ 6 — app_user пытается стать db_owner"
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
# app_user — базовые права. Нельзя быть db_owner — это прерогатива ddl_user.
|
||
step "Тест 6a: test_app_user (role=app_user) назначается db_owner"
|
||
cat > ./pg_test_invalid.tf <<'TFEOF'
|
||
resource "nubes_postgres_database" "test_appuser_as_owner" {
|
||
postgres_id = nubes_postgres.pg_test_instance.id
|
||
db_name = "appuser_owned_db"
|
||
db_owner = nubes_postgres_user.test_app_user.username
|
||
adopt_existing_on_create = false
|
||
}
|
||
TFEOF
|
||
run_apply_expect_fail "6a: app_user как db_owner — API должен отклонить"
|
||
rm -f ./pg_test_invalid.tf; run_apply > /dev/null 2>&1 || true
|
||
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
section "ИТОГ"
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
echo ""
|
||
echo -e " PASS: ${GREEN}${PASS}${NC} FAIL: ${RED}${FAIL}${NC}"
|
||
echo ""
|
||
[[ "$FAIL" -eq 0 ]] \
|
||
&& echo -e "${GREEN}Все тесты прошли.${NC}" \
|
||
|| echo -e "${RED}Есть ошибки — проверь вывод выше.${NC}"
|