chore: удалён отладочный push-sample (Harbor интеграция закончена)
This commit is contained in:
@@ -1,143 +0,0 @@
|
|||||||
# Bug Report: HTTP route is not removed after Terraform destroy
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
При удалении примера `hello-node` через Terraform команда `terraform destroy` завершается успешно, но публичный HTTP endpoint не удаляется.
|
|
||||||
|
|
||||||
Фактическое поведение после `destroy` такое:
|
|
||||||
|
|
||||||
1. Сразу после удаления endpoint ещё некоторое время отвечает `HTTP 200` и возвращает корректный ответ функции.
|
|
||||||
2. Затем backend функции действительно исчезает, но публичный маршрут остаётся опубликованным и начинает отвечать `HTTP 502 function unreachable`.
|
|
||||||
3. Даже через 120 секунд endpoint не исчезает.
|
|
||||||
|
|
||||||
Это выглядит как баг cleanup в platform/backend/provider lifecycle для HTTP trigger/route.
|
|
||||||
|
|
||||||
## Affected Example
|
|
||||||
|
|
||||||
- Example: `hello-node`
|
|
||||||
- Terraform files: `hello-node/main.tf`, `hello-node/http.tf`, `hello-node/job.tf`
|
|
||||||
- Public URL: `https://sless-api.kube5s.ru/fn/default/hello-http`
|
|
||||||
- Function name: `hello-http`
|
|
||||||
- Trigger name: `hello-http-trigger`
|
|
||||||
|
|
||||||
## Reproduction
|
|
||||||
|
|
||||||
Использовался репозиторий examples и скрипт:
|
|
||||||
|
|
||||||
- Script: `./run_terraform_examples.sh`
|
|
||||||
|
|
||||||
Шаги воспроизведения:
|
|
||||||
|
|
||||||
1. Выполнить `terraform init` в `hello-node`
|
|
||||||
2. Выполнить `terraform apply`
|
|
||||||
3. Убедиться, что endpoint живой
|
|
||||||
4. Выполнить `terraform destroy`
|
|
||||||
5. Проверять публичный URL после destroy
|
|
||||||
|
|
||||||
Логика проверки встроена в `run_terraform_examples.sh`:
|
|
||||||
|
|
||||||
1. После `apply` endpoint обязан отвечать `200`
|
|
||||||
2. После `destroy` endpoint должен исчезнуть
|
|
||||||
3. Скрипт ждёт до 120 секунд и перепроверяет endpoint каждые 5 секунд
|
|
||||||
|
|
||||||
## Expected Result
|
|
||||||
|
|
||||||
После успешного `terraform destroy`:
|
|
||||||
|
|
||||||
1. Публичный URL должен перестать существовать
|
|
||||||
2. Запрос на URL должен вернуть `404` или другой явный признак отсутствия маршрута
|
|
||||||
3. Provider не должен возвращать успешный destroy раньше, чем cleanup HTTP route завершён
|
|
||||||
|
|
||||||
## Actual Result
|
|
||||||
|
|
||||||
После успешного `terraform destroy`:
|
|
||||||
|
|
||||||
1. Terraform сообщает `Destroy complete! Resources: 4 destroyed.`
|
|
||||||
2. Endpoint `https://sless-api.kube5s.ru/fn/default/hello-http` продолжает отвечать `200`
|
|
||||||
3. Через некоторое время тот же endpoint начинает отвечать `502`
|
|
||||||
4. Тело ответа на `502`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"error":"function unreachable: Post \"http://hello-http.sless-fn-default.svc.cluster.local:8080\": dial tcp 10.106.128.167:8080: connect: operation not permitted"}
|
|
||||||
```
|
|
||||||
|
|
||||||
Это означает:
|
|
||||||
|
|
||||||
1. внешний HTTP маршрут всё ещё существует;
|
|
||||||
2. запрос по нему всё ещё направляется внутрь платформы;
|
|
||||||
3. backend функции уже удалён или недоступен;
|
|
||||||
4. cleanup маршрута не завершён.
|
|
||||||
|
|
||||||
## Timeline From Real Run
|
|
||||||
|
|
||||||
Подтверждённая последовательность из фактического прогона:
|
|
||||||
|
|
||||||
1. `terraform destroy` завершился успешно
|
|
||||||
2. первые проверки после destroy возвращали `HTTP 200`
|
|
||||||
3. затем проверки начали возвращать `HTTP 502 function unreachable`
|
|
||||||
4. в течение всех 24 проверок по 5 секунд endpoint не исчез
|
|
||||||
5. итоговое время ожидания: 120 секунд
|
|
||||||
|
|
||||||
Итоговый summary из скрипта:
|
|
||||||
|
|
||||||
```text
|
|
||||||
ERROR SUMMARY
|
|
||||||
example: hello-node
|
|
||||||
step: endpoint cleanup after clean destroy
|
|
||||||
reason: route cleanup bug: public endpoint still exists but backend is already gone (HTTP 502 function unreachable); endpoint was still published after 120s
|
|
||||||
```
|
|
||||||
|
|
||||||
## Why This Is A Real Platform Bug
|
|
||||||
|
|
||||||
Это не похоже на проблему тестового скрипта или Terraform CLI по следующим причинам:
|
|
||||||
|
|
||||||
1. `terraform destroy` завершается без ошибки
|
|
||||||
2. state Terraform очищается как ожидалось
|
|
||||||
3. сначала endpoint отвечает `200`, значит маршрут реально жив после destroy
|
|
||||||
4. потом endpoint отвечает `502 function unreachable`, значит backend уже исчез, но route ещё остался
|
|
||||||
5. скрипт ждёт 120 секунд, то есть это не мгновенная eventual consistency на 1-2 секунды
|
|
||||||
|
|
||||||
Иными словами: удаление backend и удаление публичного маршрута расходятся по времени, а route cleanup либо не выполняется, либо не дожидается завершения.
|
|
||||||
|
|
||||||
## Most Likely Broken Layer
|
|
||||||
|
|
||||||
Наиболее вероятные точки проблемы:
|
|
||||||
|
|
||||||
1. API/backend destroy trigger возвращает success до фактического удаления HTTP route
|
|
||||||
2. Controller удаляет function workload, но не удаляет route/ingress/virtualservice/gateway mapping
|
|
||||||
3. Удаление route запускается асинхронно, но его результат не awaited
|
|
||||||
4. В системе остаётся запись маршрута на имя функции, хотя service/backend уже удалён
|
|
||||||
|
|
||||||
## What To Check In The Development Repo
|
|
||||||
|
|
||||||
Нужно проверить destroy flow именно для HTTP trigger:
|
|
||||||
|
|
||||||
1. Удаляется ли объект trigger только в metadata/storage или реально удаляется и внешний маршрут
|
|
||||||
2. Какие Kubernetes/ingress объекты создаются для HTTP trigger и все ли они удаляются
|
|
||||||
3. Есть ли race condition между удалением function/service и удалением route
|
|
||||||
4. Не возвращает ли provider success раньше, чем backend подтверждает полное удаление маршрута
|
|
||||||
5. Есть ли финальный polling/wait на исчезновение route перед возвратом успешного destroy
|
|
||||||
|
|
||||||
Если архитектура использует отдельные сущности route/service/function, то destroy должен идти в таком порядке:
|
|
||||||
|
|
||||||
1. disable/remove public routing
|
|
||||||
2. дождаться, что endpoint больше не публикуется снаружи
|
|
||||||
3. удалить backend/service/workload
|
|
||||||
4. завершить destroy success
|
|
||||||
|
|
||||||
Сейчас по фактическому поведению порядок либо обратный, либо неполный.
|
|
||||||
|
|
||||||
## Minimal Acceptance Criteria For Fix
|
|
||||||
|
|
||||||
Исправление можно считать рабочим, если после `terraform destroy` для `hello-node` выполняются все условия:
|
|
||||||
|
|
||||||
1. URL `https://sless-api.kube5s.ru/fn/default/hello-http` перестаёт отвечать как живой маршрут
|
|
||||||
2. URL не возвращает `502 function unreachable`
|
|
||||||
3. URL исчезает в разумное время после destroy
|
|
||||||
4. `./run_terraform_examples.sh` проходит шаг `endpoint cleanup after clean destroy`
|
|
||||||
|
|
||||||
## Current Status
|
|
||||||
|
|
||||||
На данный момент массовый прогон examples корректно останавливается на `hello-node`, потому что это первый воспроизводимый failure.
|
|
||||||
|
|
||||||
Дальше прогонять остальные примеры без исправления destroy cleanup смысла нет: тест уже доказал platform bug на базовом HTTP сценарии.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# 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"]
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# Пример для пуша в 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`.
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
@@ -1,333 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
LOG_DIR="$ROOT_DIR/.test-logs"
|
|
||||||
mkdir -p "$LOG_DIR"
|
|
||||||
|
|
||||||
EXAMPLES=(
|
|
||||||
"hello-node"
|
|
||||||
"simple-node"
|
|
||||||
"simple-python"
|
|
||||||
"notes-python"
|
|
||||||
)
|
|
||||||
|
|
||||||
declare -A CHECK_METHOD=(
|
|
||||||
[hello-node]="POST"
|
|
||||||
[simple-node]="GET"
|
|
||||||
[simple-python]="GET"
|
|
||||||
[notes-python]="GET"
|
|
||||||
)
|
|
||||||
|
|
||||||
declare -A CHECK_URL=(
|
|
||||||
[hello-node]="https://sless-api.kube5s.ru/fn/default/hello-http"
|
|
||||||
[simple-node]="https://sless-api.kube5s.ru/fn/default/simple-node-time-display"
|
|
||||||
[simple-python]="https://sless-api.kube5s.ru/fn/default/simple-py-time-display"
|
|
||||||
[notes-python]="https://sless-api.kube5s.ru/fn/default/notes-list"
|
|
||||||
)
|
|
||||||
|
|
||||||
declare -A CHECK_DATA=(
|
|
||||||
[hello-node]='{"name":"Smoke"}'
|
|
||||||
[simple-node]=''
|
|
||||||
[simple-python]=''
|
|
||||||
[notes-python]=''
|
|
||||||
)
|
|
||||||
|
|
||||||
declare -a PREEXISTING_EXAMPLES=()
|
|
||||||
LAST_LOG_FILE=""
|
|
||||||
CURRENT_EXAMPLE=""
|
|
||||||
CURRENT_STEP=""
|
|
||||||
LAST_ERROR_SUMMARY=""
|
|
||||||
|
|
||||||
fail_run() {
|
|
||||||
local example="$1"
|
|
||||||
local step="$2"
|
|
||||||
local details="$3"
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "ERROR SUMMARY"
|
|
||||||
echo "example: $example"
|
|
||||||
echo "step: $step"
|
|
||||||
echo "reason: $details"
|
|
||||||
|
|
||||||
if [ -n "$LAST_LOG_FILE" ] && [ -f "$LAST_LOG_FILE" ]; then
|
|
||||||
echo "log: $LAST_LOG_FILE"
|
|
||||||
echo "last log lines:"
|
|
||||||
tail -n 20 "$LAST_LOG_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
run_step() {
|
|
||||||
local example="$1"
|
|
||||||
local step="$2"
|
|
||||||
shift 2
|
|
||||||
|
|
||||||
CURRENT_EXAMPLE="$example"
|
|
||||||
CURRENT_STEP="$step"
|
|
||||||
LAST_ERROR_SUMMARY=""
|
|
||||||
|
|
||||||
if ! "$@"; then
|
|
||||||
local details="$LAST_ERROR_SUMMARY"
|
|
||||||
if [ -z "$details" ]; then
|
|
||||||
details="step failed without explicit summary"
|
|
||||||
fi
|
|
||||||
fail_run "$example" "$step" "$details"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
restore_any_backups() {
|
|
||||||
local backup
|
|
||||||
while IFS= read -r backup; do
|
|
||||||
[ -n "$backup" ] || continue
|
|
||||||
if [ -f "$backup" ]; then
|
|
||||||
mv "$backup" "${backup%.copilot.bak}"
|
|
||||||
fi
|
|
||||||
done < <(find "$ROOT_DIR" -name '*.copilot.bak' | sort)
|
|
||||||
}
|
|
||||||
|
|
||||||
trap restore_any_backups EXIT
|
|
||||||
|
|
||||||
clean_local_artifacts() {
|
|
||||||
local example="$1"
|
|
||||||
rm -rf \
|
|
||||||
"$ROOT_DIR/$example/.terraform" \
|
|
||||||
"$ROOT_DIR/$example/.terraform.lock.hcl" \
|
|
||||||
"$ROOT_DIR/$example/terraform.tfstate" \
|
|
||||||
"$ROOT_DIR/$example/terraform.tfstate.backup" \
|
|
||||||
"$ROOT_DIR/$example"/terraform.tfstate.*.backup \
|
|
||||||
"$ROOT_DIR/$example/dist"
|
|
||||||
}
|
|
||||||
|
|
||||||
retry_tf() {
|
|
||||||
local example="$1"
|
|
||||||
local label="$2"
|
|
||||||
shift 2
|
|
||||||
|
|
||||||
local attempt=1
|
|
||||||
while [ "$attempt" -le 3 ]; do
|
|
||||||
LAST_LOG_FILE="$LOG_DIR/${example//\//_}-${label// /_}-${attempt}.log"
|
|
||||||
echo "==> [$example] $label (attempt $attempt/3)"
|
|
||||||
|
|
||||||
(
|
|
||||||
cd "$ROOT_DIR/$example"
|
|
||||||
"$@"
|
|
||||||
) 2>&1 | tee "$LAST_LOG_FILE"
|
|
||||||
|
|
||||||
local status=${PIPESTATUS[0]}
|
|
||||||
if [ "$status" -eq 0 ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -Eiq 'Unauthorized|401|403' "$LAST_LOG_FILE"; then
|
|
||||||
LAST_ERROR_SUMMARY="authorization error during $label"
|
|
||||||
echo "[$example] authorization error during $label"
|
|
||||||
return 41
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -Eiq 'TLS handshake timeout|tls:.*timeout|i/o timeout|Client\.Timeout exceeded while awaiting headers|context deadline exceeded|unexpected EOF' "$LAST_LOG_FILE" && [ "$attempt" -lt 3 ]; then
|
|
||||||
attempt=$((attempt + 1))
|
|
||||||
sleep 2
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -Eiq 'TLS handshake timeout|tls:.*timeout|i/o timeout|Client\.Timeout exceeded while awaiting headers|context deadline exceeded|unexpected EOF' "$LAST_LOG_FILE"; then
|
|
||||||
LAST_ERROR_SUMMARY="network/provider download failure during $label after retries"
|
|
||||||
else
|
|
||||||
LAST_ERROR_SUMMARY="terraform command failed during $label with exit code $status"
|
|
||||||
fi
|
|
||||||
|
|
||||||
return "$status"
|
|
||||||
done
|
|
||||||
|
|
||||||
LAST_ERROR_SUMMARY="terraform command failed during $label after exhausting retries"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
record_preexisting_if_needed() {
|
|
||||||
local example="$1"
|
|
||||||
if grep -Fq 'No changes. Your infrastructure matches the configuration.' "$LAST_LOG_FILE"; then
|
|
||||||
PREEXISTING_EXAMPLES+=("$example")
|
|
||||||
echo "[$example] detected preexisting remote resources on clean apply"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
probe_endpoint() {
|
|
||||||
local example="$1"
|
|
||||||
local body_file="$LOG_DIR/${example//\//_}-endpoint-body.txt"
|
|
||||||
local status_file="$LOG_DIR/${example//\//_}-endpoint-status.txt"
|
|
||||||
local method="${CHECK_METHOD[$example]}"
|
|
||||||
local url="${CHECK_URL[$example]}"
|
|
||||||
local data="${CHECK_DATA[$example]}"
|
|
||||||
|
|
||||||
if [ "$method" = "POST" ]; then
|
|
||||||
curl -sS -X POST -H 'Content-Type: application/json' -d "$data" -o "$body_file" -w '%{http_code}' "$url" > "$status_file"
|
|
||||||
else
|
|
||||||
curl -sS -o "$body_file" -w '%{http_code}' "$url" > "$status_file"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_live_endpoint() {
|
|
||||||
local example="$1"
|
|
||||||
probe_endpoint "$example"
|
|
||||||
|
|
||||||
local body_file="$LOG_DIR/${example//\//_}-endpoint-body.txt"
|
|
||||||
local status
|
|
||||||
status="$(cat "$LOG_DIR/${example//\//_}-endpoint-status.txt")"
|
|
||||||
|
|
||||||
if [ "$status" != "200" ]; then
|
|
||||||
LAST_ERROR_SUMMARY="live endpoint check failed with HTTP $status"
|
|
||||||
echo "[$example] live endpoint check failed with HTTP $status"
|
|
||||||
cat "$body_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -Fq 'function unreachable' "$body_file"; then
|
|
||||||
LAST_ERROR_SUMMARY="live endpoint returned function unreachable"
|
|
||||||
echo "[$example] live endpoint check returned unreachable function"
|
|
||||||
cat "$body_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_destroyed_endpoint() {
|
|
||||||
local example="$1"
|
|
||||||
probe_endpoint "$example"
|
|
||||||
|
|
||||||
local body_file="$LOG_DIR/${example//\//_}-endpoint-body.txt"
|
|
||||||
local status
|
|
||||||
status="$(cat "$LOG_DIR/${example//\//_}-endpoint-status.txt")"
|
|
||||||
|
|
||||||
if [ "$status" = "404" ] || [ "$status" = "000" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -Eiq 'not found|404 page not found' "$body_file"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$status" = "502" ] && grep -Fq 'function unreachable' "$body_file"; then
|
|
||||||
LAST_ERROR_SUMMARY="route cleanup bug: public endpoint still exists but backend is already gone (HTTP 502 function unreachable)"
|
|
||||||
echo "[$example] route still exists after destroy, but backend is already gone (HTTP 502 function unreachable)"
|
|
||||||
cat "$body_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
LAST_ERROR_SUMMARY="endpoint still responds after destroy with HTTP $status"
|
|
||||||
echo "[$example] endpoint still responds after destroy with HTTP $status"
|
|
||||||
cat "$body_file"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_for_destroyed_endpoint() {
|
|
||||||
local example="$1"
|
|
||||||
local attempts=24
|
|
||||||
local sleep_sec=5
|
|
||||||
local try=1
|
|
||||||
|
|
||||||
while [ "$try" -le "$attempts" ]; do
|
|
||||||
if assert_destroyed_endpoint "$example"; then
|
|
||||||
echo "[$example] endpoint disappeared after destroy"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[$example] endpoint still present after destroy, waiting (${try}/${attempts})"
|
|
||||||
try=$((try + 1))
|
|
||||||
sleep "$sleep_sec"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "[$example] endpoint did not disappear after destroy within $((attempts * sleep_sec))s"
|
|
||||||
if [ -z "$LAST_ERROR_SUMMARY" ]; then
|
|
||||||
LAST_ERROR_SUMMARY="endpoint remained reachable for more than $((attempts * sleep_sec))s after destroy"
|
|
||||||
else
|
|
||||||
LAST_ERROR_SUMMARY="$LAST_ERROR_SUMMARY; endpoint was still published after $((attempts * sleep_sec))s"
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
backup_and_modify() {
|
|
||||||
local example="$1"
|
|
||||||
case "$example" in
|
|
||||||
hello-node)
|
|
||||||
cp "$ROOT_DIR/$example/http.tf" "$ROOT_DIR/$example/http.tf.copilot.bak"
|
|
||||||
perl -0pi -e 's/enabled\s+=\s+true/enabled = false/' "$ROOT_DIR/$example/http.tf"
|
|
||||||
;;
|
|
||||||
simple-node)
|
|
||||||
cp "$ROOT_DIR/$example/time-display.tf" "$ROOT_DIR/$example/time-display.tf.copilot.bak"
|
|
||||||
perl -0pi -e 's/memory_mb\s+=\s+64/memory_mb = 96/' "$ROOT_DIR/$example/time-display.tf"
|
|
||||||
;;
|
|
||||||
simple-python)
|
|
||||||
cp "$ROOT_DIR/$example/time-display.tf" "$ROOT_DIR/$example/time-display.tf.copilot.bak"
|
|
||||||
perl -0pi -e 's/memory_mb\s+=\s+64/memory_mb = 96/' "$ROOT_DIR/$example/time-display.tf"
|
|
||||||
;;
|
|
||||||
notes-python)
|
|
||||||
cp "$ROOT_DIR/$example/notes-list.tf" "$ROOT_DIR/$example/notes-list.tf.copilot.bak"
|
|
||||||
perl -0pi -e 's/memory_mb\s+=\s+128/memory_mb = 160/' "$ROOT_DIR/$example/notes-list.tf"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
restore_modified_files() {
|
|
||||||
local example="$1"
|
|
||||||
case "$example" in
|
|
||||||
hello-node)
|
|
||||||
mv "$ROOT_DIR/$example/http.tf.copilot.bak" "$ROOT_DIR/$example/http.tf"
|
|
||||||
;;
|
|
||||||
simple-node)
|
|
||||||
mv "$ROOT_DIR/$example/time-display.tf.copilot.bak" "$ROOT_DIR/$example/time-display.tf"
|
|
||||||
;;
|
|
||||||
simple-python)
|
|
||||||
mv "$ROOT_DIR/$example/time-display.tf.copilot.bak" "$ROOT_DIR/$example/time-display.tf"
|
|
||||||
;;
|
|
||||||
notes-python)
|
|
||||||
mv "$ROOT_DIR/$example/notes-list.tf.copilot.bak" "$ROOT_DIR/$example/notes-list.tf"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
run_example() {
|
|
||||||
local example="$1"
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "==== $example ===="
|
|
||||||
|
|
||||||
clean_local_artifacts "$example"
|
|
||||||
run_step "$example" "terraform init" retry_tf "$example" "terraform init" terraform init -input=false -no-color
|
|
||||||
run_step "$example" "terraform apply clean" retry_tf "$example" "terraform apply clean" terraform apply -auto-approve -input=false -no-color
|
|
||||||
record_preexisting_if_needed "$example"
|
|
||||||
run_step "$example" "endpoint check after clean apply" assert_live_endpoint "$example"
|
|
||||||
|
|
||||||
run_step "$example" "terraform destroy clean" retry_tf "$example" "terraform destroy clean" terraform destroy -auto-approve -input=false -no-color
|
|
||||||
run_step "$example" "endpoint cleanup after clean destroy" wait_for_destroyed_endpoint "$example"
|
|
||||||
|
|
||||||
run_step "$example" "terraform apply second" retry_tf "$example" "terraform apply second" terraform apply -auto-approve -input=false -no-color
|
|
||||||
run_step "$example" "endpoint check after second apply" assert_live_endpoint "$example"
|
|
||||||
|
|
||||||
backup_and_modify "$example"
|
|
||||||
run_step "$example" "terraform apply modified" retry_tf "$example" "terraform apply modified" terraform apply -auto-approve -input=false -no-color
|
|
||||||
restore_modified_files "$example"
|
|
||||||
|
|
||||||
run_step "$example" "terraform destroy final" retry_tf "$example" "terraform destroy final" terraform destroy -auto-approve -input=false -no-color
|
|
||||||
run_step "$example" "endpoint cleanup after final destroy" wait_for_destroyed_endpoint "$example"
|
|
||||||
clean_local_artifacts "$example"
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
|
||||||
local example
|
|
||||||
for example in "${EXAMPLES[@]}"; do
|
|
||||||
run_example "$example"
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "${#PREEXISTING_EXAMPLES[@]}" -gt 0 ]; then
|
|
||||||
echo
|
|
||||||
echo "Preexisting remote resources were detected on first apply for: ${PREEXISTING_EXAMPLES[*]}"
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "All Terraform example lifecycles completed successfully."
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
Reference in New Issue
Block a user