test: E2E скрипт run_e2e_tests.sh + fix provider token/version в примерах
This commit is contained in:
@@ -175,6 +175,24 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-11 — E2E тесты через скрипт run_e2e_tests.sh
|
||||
|
||||
| Пример | init | apply | modify + apply | destroy | Итог |
|
||||
|--------|------|-------|----------------|---------|------|
|
||||
| hello-node | ✅ | ✅ | ✅ memory 128→256 MB | ✅ 4 destroyed | **PASS** |
|
||||
| simple-node | ✅ | ✅ | ✅ memory 64→96 MB | ✅ 4 destroyed | **PASS** |
|
||||
|
||||
**Скрипт:** `run_e2e_tests.sh` в корне репы
|
||||
**Возможности:** retry 3× при TLS timeout, emergency destroy trap, логи в `.e2e-logs/`
|
||||
**Провайдер в примерах:** токен через `var.token` + `terraform.tfvars` (gitignored), version `~> 0.1.13`
|
||||
|
||||
**Наблюдения:**
|
||||
- Периодические TLS handshake timeout на `sless-api.kube5s.ru` — сеть нестабильна, retry помогает
|
||||
- `terra.k8c.ru` иногда даёт `unexpected EOF` при скачивании провайдера — то же, retry
|
||||
- Namespace `sless-cdd874dfa31ba6ca` жив после всех destroy — поведение корректное
|
||||
|
||||
---
|
||||
|
||||
## Остаток технического долга (не блокирует)
|
||||
|
||||
| # | Что | Приоритет |
|
||||
|
||||
@@ -18,7 +18,7 @@ terraform {
|
||||
|
||||
provider "sless" {
|
||||
endpoint = "https://sless-api.kube5s.ru"
|
||||
token = file("${path.module}/../../secrets/prod.token")
|
||||
token = var.token
|
||||
nubes_endpoint = "https://deck-api.ngcloud.ru/api/v1"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# 2026-03-11
|
||||
# variables.tf — входные переменные для hello-node примера.
|
||||
|
||||
# JWT токен облака (nubes). Передаётся через terraform.tfvars (gitignored).
|
||||
# Из токена провайдер вычисляет namespace: sless-{sha256[:8]}
|
||||
variable "token" {
|
||||
description = "JWT токен облака для аутентификации в sless API"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
@@ -14,13 +14,14 @@ terraform {
|
||||
# Провайдер для управления serverless функциями через sless API
|
||||
sless = {
|
||||
source = "terra.k8c.ru/naeel/sless"
|
||||
version = "~> 0.1.11"
|
||||
version = "~> 0.1.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# sless провайдер подключается к API кластера.
|
||||
provider "sless" {
|
||||
endpoint = "https://sless-api.kube5s.ru"
|
||||
token = "dev-token-change-me"
|
||||
endpoint = "https://sless-api.kube5s.ru"
|
||||
token = var.token
|
||||
nubes_endpoint = "https://deck-api.ngcloud.ru/api/v1"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
# 2026-03-09
|
||||
# 2026-03-09 (обновлён 2026-03-11)
|
||||
# variables.tf — входные переменные для notes-python примера.
|
||||
#
|
||||
# PG_DSN передаётся во все функции через env_vars.
|
||||
# Хранится как sensitive чтобы не светился в terraform output и логах.
|
||||
# В продакшне — не хардкоди DSN здесь, используй TF_VAR_pg_dsn или secrets manager.
|
||||
|
||||
# JWT токен облака (nubes). Передаётся через terraform.tfvars (gitignored).
|
||||
# Из токена провайдер вычисляет namespace: sless-{sha256[:8]}
|
||||
variable "token" {
|
||||
description = "JWT токен облака для аутентификации в sless API"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
# DSN для подключения к PostgreSQL внутри кластера.
|
||||
# Формат: postgres://user:password@host:port/dbname?sslmode=...
|
||||
variable "pg_dsn" {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# 2026-03-11 10:00
|
||||
# Minimal sample image to push to PearlHarbor registry
|
||||
# Purpose: небольшой образ для тестирования пуша в реестр
|
||||
|
||||
FROM alpine:3.18
|
||||
|
||||
CMD ["sh", "-c", "echo Hello from pearlharbor sample image"]
|
||||
@@ -0,0 +1,28 @@
|
||||
# Пример для пуша в PearlHarbor
|
||||
|
||||
Файлы:
|
||||
- [examples/push-sample/Dockerfile](examples/push-sample/Dockerfile) — минимальный образ
|
||||
- [examples/push-sample/build_and_push.sh](examples/push-sample/build_and_push.sh) — сборка и опциональный пуш
|
||||
|
||||
Как использовать:
|
||||
|
||||
1. Сборка локально (в корне репы):
|
||||
|
||||
```bash
|
||||
docker build -t sless-sample:local -f examples/push-sample/Dockerfile examples/push-sample
|
||||
```
|
||||
|
||||
2. Протестировать скрипт (скрипт не будет пушить без переменной DO_PUSH):
|
||||
|
||||
```bash
|
||||
cd examples/push-sample
|
||||
./build_and_push.sh
|
||||
```
|
||||
|
||||
3. Для реального пуша установите `DO_PUSH=true`. Скрипт прочитает `secrets/pearlharbor_registry.txt`.
|
||||
|
||||
```bash
|
||||
DO_PUSH=true ./build_and_push.sh
|
||||
```
|
||||
|
||||
Примечание: скрипт использует по умолчанию пользователя `admin`. Для другого пользователя задайте `REGISTRY_USER`.
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
# 2026-03-11 10:02
|
||||
# Скрипт: собирает минимальный образ и, при разрешении, пушит в реестр PearlHarbor
|
||||
# Требования: `docker` в PATH. Скрипт НЕ будет пушить без DO_PUSH=true.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Получаем значения из файла секретов
|
||||
SECRETS_FILE="secrets/pearlharbor_registry.txt"
|
||||
if [ ! -f "$SECRETS_FILE" ]; then
|
||||
echo "Файл с секретами не найден: $SECRETS_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
connection_url=$(grep -E '^connection_url=' "$SECRETS_FILE" | cut -d'=' -f2-)
|
||||
admin_pass=$(grep -E '^admin_pass=' "$SECRETS_FILE" | cut -d'=' -f2-)
|
||||
|
||||
if [ -z "$connection_url" ]; then
|
||||
echo "Не найден connection_url в $SECRETS_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Убираем протокол и возможный слеш на конце
|
||||
registry_host=$(echo "$connection_url" | sed -E 's~https?://~~' | sed -E 's~/$~~')
|
||||
|
||||
image_name="$registry_host/sless-sample:latest"
|
||||
|
||||
echo "Registry host: $registry_host"
|
||||
echo "Image name: $image_name"
|
||||
|
||||
echo "Собираю образ локально..."
|
||||
docker build -t sless-sample:local -f Dockerfile .. || {
|
||||
echo "Сборка не удалась"; exit 1
|
||||
}
|
||||
|
||||
echo "Готово. Образ: sless-sample:local"
|
||||
|
||||
if [ "${DO_PUSH:-}" != "true" ]; then
|
||||
echo "DO_PUSH != true — пуш не будет выполнен. Чтобы запушить: DO_PUSH=true ./build_and_push.sh"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Если дошли до сюда — выполняем login/push
|
||||
registry_user=${REGISTRY_USER:-admin}
|
||||
|
||||
echo "Выполняю docker login к $registry_host как '$registry_user'"
|
||||
echo "$admin_pass" | docker login "$registry_host" -u "$registry_user" --password-stdin
|
||||
|
||||
echo "Тегирую и пушу образ: $image_name"
|
||||
docker tag sless-sample:local "$image_name"
|
||||
docker push "$image_name"
|
||||
|
||||
echo "Пуш завершён. Проверьте реестр для образа: $image_name"
|
||||
@@ -19,12 +19,13 @@ terraform {
|
||||
required_providers {
|
||||
sless = {
|
||||
source = "terra.k8c.ru/naeel/sless"
|
||||
version = "~> 0.1.11"
|
||||
version = "~> 0.1.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "sless" {
|
||||
endpoint = "https://sless-api.kube5s.ru"
|
||||
token = "dev-token-change-me"
|
||||
endpoint = "https://sless-api.kube5s.ru"
|
||||
token = var.token
|
||||
nubes_endpoint = "https://deck-api.ngcloud.ru/api/v1"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# 2026-03-11
|
||||
# variables.tf — входные переменные для simple-node примера.
|
||||
|
||||
# JWT токен облака (nubes). Передаётся через terraform.tfvars (gitignored).
|
||||
# Из токена провайдер вычисляет namespace: sless-{sha256[:8]}
|
||||
variable "token" {
|
||||
description = "JWT токен облака для аутентификации в sless API"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
@@ -18,12 +18,13 @@ terraform {
|
||||
required_providers {
|
||||
sless = {
|
||||
source = "terra.k8c.ru/naeel/sless"
|
||||
version = "~> 0.1.11"
|
||||
version = "~> 0.1.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "sless" {
|
||||
endpoint = "https://sless-api.kube5s.ru"
|
||||
token = "dev-token-change-me"
|
||||
endpoint = "https://sless-api.kube5s.ru"
|
||||
token = var.token
|
||||
nubes_endpoint = "https://deck-api.ngcloud.ru/api/v1"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# 2026-03-11
|
||||
# variables.tf — входные переменные для simple-python примера.
|
||||
|
||||
# JWT токен облака (nubes). Передаётся через terraform.tfvars (gitignored).
|
||||
# Из токена провайдер вычисляет namespace: sless-{sha256[:8]}
|
||||
variable "token" {
|
||||
description = "JWT токен облака для аутентификации в sless API"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
Executable
+256
@@ -0,0 +1,256 @@
|
||||
#!/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 <logfile> <terraform args...>
|
||||
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
|
||||
Reference in New Issue
Block a user