fix(ci): ждать настоящий Postgres, а не временный сервер фазы initdb
All checks were successful
CI / changes (pull_request) Successful in 9s
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Successful in 52s
CI Trade-In / frontend-checks (pull_request) Successful in 1m38s
CI / frontend-tests (pull_request) Successful in 1m44s
CI / openapi-codegen-check (pull_request) Successful in 2m20s
CI Trade-In / backend-tests (pull_request) Successful in 4m31s
CI / backend-tests (pull_request) Successful in 15m35s
All checks were successful
CI / changes (pull_request) Successful in 9s
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Successful in 52s
CI Trade-In / frontend-checks (pull_request) Successful in 1m38s
CI / frontend-tests (pull_request) Successful in 1m44s
CI / openapi-codegen-check (pull_request) Successful in 2m20s
CI Trade-In / backend-tests (pull_request) Successful in 4m31s
CI / backend-tests (pull_request) Successful in 15m35s
Прогон 6859: контейнер поднялся, `pg_isready` сказал «accepting connections», через 1.4с bootstrap упал на `container is not running`. Причина — проба шла через unix-сокет, а по сокету отвечает ВРЕМЕННЫЙ сервер, который образ postgres поднимает на время initdb с listen_addresses=''. Снаружи БД в этот момент ещё не существует, и впереди рестарт: мы поймали окно и приняли его за готовность. Классическая ложная зелень — проверка сказала «готово» про не то. Теперь проба `pg_isready -h 127.0.0.1` (TCP) — зеленеет только на настоящем сервере, том самом, к которому пойдут тесты. Плюс: цикл ждёт до 90с и прерывается, если контейнер вышел; при неудаче печатается статус, код выхода и `docker logs`. Подъём и bootstrap слиты в ОДИН шаг — между шагами контейнер успевал исчезнуть.
This commit is contained in:
parent
a07da68a37
commit
cfde033c2f
2 changed files with 63 additions and 37 deletions
|
|
@ -86,57 +86,69 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Поднять Postgres для тестов
|
- name: Поднять Postgres и собрать схему tradein
|
||||||
working-directory: .
|
working-directory: .
|
||||||
# ПОЧЕМУ НЕ `services:` И ПОЧЕМУ БЕЗ ПУБЛИКАЦИИ ПОРТА.
|
# ПОЧЕМУ НЕ `services:` И ПОЧЕМУ БЕЗ ПУБЛИКАЦИИ ПОРТА.
|
||||||
# Раннер запускает и job, и сервис-контейнеры с `--network host` (видно в
|
# Раннер запускает и job, и сервис-контейнеры с `--network host` (видно в
|
||||||
# логе прогона: `docker create image=... network="host"`), а на этом же
|
# логе прогона: `docker create image=... network="host"`), а на 5432 того
|
||||||
# хосте на 5432 уже слушает ПРОДОВЫЙ Postgres. Первая попытка через
|
# же хоста слушает ПРОДОВЫЙ Postgres. Попытка через `services:` +
|
||||||
# `services:` + `ports: 5432:5432` (прогон #6853) кончилась тем, что
|
# `ports: 5432:5432` кончилась тем, что сервис-контейнер не смог занять
|
||||||
# сервис-контейнер не смог занять порт, а psql из job'а ушёл В ПРОД и
|
# порт, а psql из job'а ушёл В ПРОД и получил
|
||||||
# получил `password authentication failed for user "tradein"`. То есть на
|
# `password authentication failed for user "tradein"`. То есть
|
||||||
# этом раннере `localhost:5432` — это боевая база, а не тестовая.
|
# `localhost:5432` из job'а на этом раннере — боевая база, а не тестовая.
|
||||||
|
# Поэтому контейнер поднимаем сами, в bridge-сети, БЕЗ публикации порта,
|
||||||
|
# и ходим по его собственному IP: прод недостижим в принципе, параллельные
|
||||||
|
# прогоны не конфликтуют, psql берём из самого контейнера.
|
||||||
#
|
#
|
||||||
# Поэтому: контейнер поднимаем сами, в дефолтной bridge-сети, БЕЗ
|
# ОДИН шаг, а не два: между шагами контейнер успевал исчезнуть, и
|
||||||
# публикации порта, и ходим по его собственному IP. Ноль шансов задеть
|
# bootstrap падал на `container is not running`.
|
||||||
# прод, ноль конфликтов между параллельными прогонами и никакого
|
#
|
||||||
# postgresql-client на раннере — psql берём из самого контейнера.
|
# `pg_isready -h 127.0.0.1`, а НЕ через unix-сокет: на время initdb образ
|
||||||
|
# поднимает ВРЕМЕННЫЙ сервер с listen_addresses='' — по сокету он уже
|
||||||
|
# отвечает «accepting connections», хотя снаружи БД ещё не существует, а
|
||||||
|
# впереди рестарт. Проба по TCP зеленеет только на настоящем сервере —
|
||||||
|
# том самом, к которому пойдут тесты.
|
||||||
#
|
#
|
||||||
# postgis, не plain postgres: tests/tasks/test_cadastral_geo_match.py
|
# postgis, не plain postgres: tests/tasks/test_cadastral_geo_match.py
|
||||||
# проверяет KNN по geometry (PostGIS_Version() в connectivity-probe).
|
# проверяет KNN по geometry (PostGIS_Version() в connectivity-probe).
|
||||||
#
|
|
||||||
# Имя БД ОБЯЗАНО отличаться от `test`: `_live_session()` считает DSN с
|
# Имя БД ОБЯЗАНО отличаться от `test`: `_live_session()` считает DSN с
|
||||||
# `localhost:5432/test` заглушкой и возвращает None — иначе контейнер
|
# `localhost:5432/test` заглушкой и вернул бы None — контейнер поднялся
|
||||||
# поднялся бы, а тесты всё равно скипались. Пароль — одноразовый,
|
# бы, а тесты всё равно скипались.
|
||||||
# CI-локальный, контейнер живёт минуты и наружу не смотрит.
|
|
||||||
run: |
|
run: |
|
||||||
|
set -u
|
||||||
docker rm -f "$CI_PG" >/dev/null 2>&1 || true
|
docker rm -f "$CI_PG" >/dev/null 2>&1 || true
|
||||||
docker run -d --name "$CI_PG" \
|
docker run -d --name "$CI_PG" \
|
||||||
-e POSTGRES_DB=tradein -e POSTGRES_USER=tradein -e POSTGRES_PASSWORD=tradein \
|
-e POSTGRES_DB=tradein -e POSTGRES_USER=tradein -e POSTGRES_PASSWORD=tradein \
|
||||||
postgis/postgis:16-3.4
|
postgis/postgis:16-3.4
|
||||||
for _ in $(seq 1 30); do
|
|
||||||
docker exec "$CI_PG" pg_isready -U tradein -q && break
|
ready=""
|
||||||
|
for _ in $(seq 1 45); do
|
||||||
|
if docker exec "$CI_PG" pg_isready -h 127.0.0.1 -U tradein -q 2>/dev/null; then
|
||||||
|
ready=1; break
|
||||||
|
fi
|
||||||
|
[ "$(docker inspect -f '{{.State.Status}}' "$CI_PG" 2>/dev/null)" = "running" ] || break
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
docker exec "$CI_PG" pg_isready -U tradein \
|
if [ -z "$ready" ]; then
|
||||||
|| { echo "::error::Postgres не поднялся за 60с"; docker logs "$CI_PG"; exit 1; }
|
echo "::error::Postgres не поднялся; статус=$(docker inspect -f '{{.State.Status}} exit={{.State.ExitCode}}' "$CI_PG" 2>&1)"
|
||||||
|
docker logs --tail 50 "$CI_PG" 2>&1 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CI_PG")
|
ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CI_PG")
|
||||||
[ -n "$ip" ] || { echo "::error::не удалось узнать IP контейнера $CI_PG"; exit 1; }
|
[ -n "$ip" ] || { echo "::error::не удалось узнать IP контейнера $CI_PG"; exit 1; }
|
||||||
echo "DATABASE_URL=postgresql+psycopg://tradein:tradein@${ip}:5432/tradein" >> "$GITHUB_ENV"
|
echo "DATABASE_URL=postgresql+psycopg://tradein:tradein@${ip}:5432/tradein" >> "$GITHUB_ENV"
|
||||||
echo "✓ Postgres на ${ip}:5432 (контейнер $CI_PG)"
|
echo "✓ Postgres на ${ip}:5432 (контейнер $CI_PG)"
|
||||||
|
|
||||||
- name: Bootstrap схемы tradein (backend/data/sql/*.sql)
|
# Тот же порядок и тот же строгий режим, что в deploy-tradein.yml:
|
||||||
working-directory: ./tradein-mvp
|
# `ls | sort` + ON_ERROR_STOP=on, падение любой миграции → job RED.
|
||||||
# Тот же порядок и тот же строгий режим, что в deploy-tradein.yml:
|
# Никаких «применилось как получилось»: схема в CI либо та же, что на
|
||||||
# `ls | sort` + ON_ERROR_STOP=on, падение любой миграции → job RED.
|
# проде, либо гейта нет.
|
||||||
# Никаких «применилось как получилось»: схема в CI либо та же, что на
|
|
||||||
# проде, либо гейта нет.
|
|
||||||
run: |
|
|
||||||
docker exec -i "$CI_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -q -c \
|
docker exec -i "$CI_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -q -c \
|
||||||
"CREATE EXTENSION IF NOT EXISTS postgis;
|
"CREATE EXTENSION IF NOT EXISTS postgis;
|
||||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||||
CREATE ROLE gendesign_reader;"
|
CREATE ROLE gendesign_reader;"
|
||||||
for sql_file in $(ls -1 backend/data/sql/*.sql | sort); do
|
for sql_file in $(ls -1 tradein-mvp/backend/data/sql/*.sql | sort); do
|
||||||
fname=$(basename "$sql_file")
|
fname=$(basename "$sql_file")
|
||||||
# ЕДИНСТВЕННОЕ исключение, и оно названо вслух: 077 — не DDL, а
|
# ЕДИНСТВЕННОЕ исключение, и оно названо вслух: 077 — не DDL, а
|
||||||
# backfill, читающий foreign table gendesign_rosreestr_deals из БД
|
# backfill, читающий foreign table gendesign_rosreestr_deals из БД
|
||||||
|
|
@ -147,7 +159,7 @@ jobs:
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
docker exec -i "$CI_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -q < "$sql_file" \
|
docker exec -i "$CI_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -q < "$sql_file" \
|
||||||
|| { echo "::error::миграция $fname не применилась"; exit 1; }
|
|| { echo "::error::миграция $fname не применилась"; docker logs --tail 20 "$CI_PG" 2>&1 || true; exit 1; }
|
||||||
done
|
done
|
||||||
echo "✓ схема собрана: $(docker exec "$CI_PG" psql -U tradein -d tradein -tAc \
|
echo "✓ схема собрана: $(docker exec "$CI_PG" psql -U tradein -d tradein -tAc \
|
||||||
"SELECT count(*) FROM information_schema.tables WHERE table_schema='public'") таблиц"
|
"SELECT count(*) FROM information_schema.tables WHERE table_schema='public'") таблиц"
|
||||||
|
|
|
||||||
|
|
@ -98,26 +98,40 @@ jobs:
|
||||||
|
|
||||||
- name: Поднять Postgres для тестов
|
- name: Поднять Postgres для тестов
|
||||||
working-directory: .
|
working-directory: .
|
||||||
# ПОЧЕМУ НЕ `services:` И ПОЧЕМУ БЕЗ ПУБЛИКАЦИИ ПОРТА — см. подробный
|
# ПОЧЕМУ НЕ `services:` И ПОЧЕМУ БЕЗ ПУБЛИКАЦИИ ПОРТА — подробный разбор в
|
||||||
# разбор в ci-tradein.yml (тот же раннер). Кратко: job и сервис-контейнеры
|
# ci-tradein.yml (тот же раннер). Кратко: job и сервис-контейнеры идут с
|
||||||
# запускаются с `--network host`, а на 5432 этого хоста слушает ПРОДОВЫЙ
|
# `--network host`, а на 5432 этого хоста слушает ПРОДОВЫЙ Postgres, то
|
||||||
# Postgres, так что `localhost:5432` из job'а — боевая база. Поднимаем
|
# есть `localhost:5432` из job'а — боевая база. Поднимаем контейнер сами,
|
||||||
# контейнер сами, в bridge-сети, без публикации порта, ходим по его IP.
|
# в bridge-сети, без публикации порта, ходим по его IP.
|
||||||
|
#
|
||||||
|
# `pg_isready -h 127.0.0.1`, а не через unix-сокет: по сокету отвечает
|
||||||
|
# ВРЕМЕННЫЙ сервер фазы initdb (listen_addresses=''), после которой БД
|
||||||
|
# ещё перезапускается. Проба по TCP зеленеет только на настоящем сервере.
|
||||||
#
|
#
|
||||||
# plain postgres:16, БЕЗ PostGIS: тесты tests/sql/ строят себе временные
|
# plain postgres:16, БЕЗ PostGIS: тесты tests/sql/ строят себе временные
|
||||||
# таблицы и не трогают ни geometry, ни реальную схему — bootstrap схемы
|
# таблицы и не трогают ни geometry, ни реальную схему — bootstrap схемы
|
||||||
# здесь не нужен вовсе, в отличие от tradein-лэйна.
|
# здесь не нужен вовсе, в отличие от tradein-лэйна.
|
||||||
run: |
|
run: |
|
||||||
|
set -u
|
||||||
docker rm -f "$CI_PG" >/dev/null 2>&1 || true
|
docker rm -f "$CI_PG" >/dev/null 2>&1 || true
|
||||||
docker run -d --name "$CI_PG" \
|
docker run -d --name "$CI_PG" \
|
||||||
-e POSTGRES_DB=gendesign_ci -e POSTGRES_USER=gendesign -e POSTGRES_PASSWORD=gendesign \
|
-e POSTGRES_DB=gendesign_ci -e POSTGRES_USER=gendesign -e POSTGRES_PASSWORD=gendesign \
|
||||||
postgres:16
|
postgres:16
|
||||||
for _ in $(seq 1 30); do
|
|
||||||
docker exec "$CI_PG" pg_isready -U gendesign -q && break
|
ready=""
|
||||||
|
for _ in $(seq 1 45); do
|
||||||
|
if docker exec "$CI_PG" pg_isready -h 127.0.0.1 -U gendesign -q 2>/dev/null; then
|
||||||
|
ready=1; break
|
||||||
|
fi
|
||||||
|
[ "$(docker inspect -f '{{.State.Status}}' "$CI_PG" 2>/dev/null)" = "running" ] || break
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
docker exec "$CI_PG" pg_isready -U gendesign \
|
if [ -z "$ready" ]; then
|
||||||
|| { echo "::error::Postgres не поднялся за 60с"; docker logs "$CI_PG"; exit 1; }
|
echo "::error::Postgres не поднялся; статус=$(docker inspect -f '{{.State.Status}} exit={{.State.ExitCode}}' "$CI_PG" 2>&1)"
|
||||||
|
docker logs --tail 50 "$CI_PG" 2>&1 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CI_PG")
|
ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CI_PG")
|
||||||
[ -n "$ip" ] || { echo "::error::не удалось узнать IP контейнера $CI_PG"; exit 1; }
|
[ -n "$ip" ] || { echo "::error::не удалось узнать IP контейнера $CI_PG"; exit 1; }
|
||||||
echo "DATABASE_URL=postgresql+psycopg://gendesign:gendesign@${ip}:5432/gendesign_ci" >> "$GITHUB_ENV"
|
echo "DATABASE_URL=postgresql+psycopg://gendesign:gendesign@${ip}:5432/gendesign_ci" >> "$GITHUB_ENV"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue