fix(ci): тестовый Postgres — свой контейнер, а не services: на порту прода
Some checks failed
CI / changes (pull_request) Successful in 9s
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Failing after 20s
CI Trade-In / browser-tests (pull_request) Successful in 57s
CI / frontend-tests (pull_request) Successful in 1m39s
CI Trade-In / frontend-checks (pull_request) Successful in 1m37s
CI / openapi-codegen-check (pull_request) Successful in 2m37s
CI / backend-tests (pull_request) Successful in 16m7s
Some checks failed
CI / changes (pull_request) Successful in 9s
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Failing after 20s
CI Trade-In / browser-tests (pull_request) Successful in 57s
CI / frontend-tests (pull_request) Successful in 1m39s
CI Trade-In / frontend-checks (pull_request) Successful in 1m37s
CI / openapi-codegen-check (pull_request) Successful in 2m37s
CI / backend-tests (pull_request) Successful in 16m7s
Первая версия (#2745, прогон 6853) упала, и упала полезно: `services:` + `ports: 5432:5432` на этом раннере означает не то, что кажется. Раннер запускает и job, и сервис-контейнеры с `--network host` (в логе: `docker create image=... network="host"`), а на 5432 того же хоста слушает ПРОДОВЫЙ Postgres. Сервис-контейнер порт занять не смог, а psql из job'а ушёл в прод и получил `password authentication failed for user "tradein"`. То есть `localhost:5432` из CI-job'а на vps-runner — это боевая база. Тест, чьи креды случайно подошли бы, писал бы в прод. Поэтому здесь нельзя публиковать порты вообще. Теперь контейнер поднимается явным `docker run` в дефолтной bridge-сети, БЕЗ `-p`, а DSN собирается из его собственного IP и уезжает в $GITHUB_ENV. Плюсы помимо безопасности: имя контейнера содержит github.run_id (параллельные PR не дерутся), psql берётся из самого контейнера (ушёл apt-get postgresql-client), снос контейнера в шаге с if: always().
This commit is contained in:
parent
1fa3cb6949
commit
a07da68a37
2 changed files with 92 additions and 51 deletions
|
|
@ -75,48 +75,64 @@ jobs:
|
|||
# Замер перед включением: полный сьют на mock-лэйне 122с / 3858 passed / 10 skipped,
|
||||
# тот же сьют против живой БД — 106с / 3867 passed / 1 skipped. Живая БД не
|
||||
# медленнее, поэтому НЕ добавляем второй job, а чиним этот: один прогон, на
|
||||
# девять реальных проверок больше. Накладные — только подъём сервиса и bootstrap
|
||||
# схемы (219 файлов, ~20с).
|
||||
services:
|
||||
postgres:
|
||||
# postgis, не plain postgres: tests/tasks/test_cadastral_geo_match.py
|
||||
# проверяет KNN по geometry (PostGIS_Version() в connectivity-probe).
|
||||
image: postgis/postgis:16-3.4
|
||||
env:
|
||||
# CI-локальная одноразовая БД, не секрет (тот же паттерн, что в
|
||||
# .github/workflows/ci.yml). Имя БД ОБЯЗАНО отличаться от `test`:
|
||||
# `_live_session()` считает DSN с `localhost:5432/test` заглушкой и
|
||||
# возвращает None — с ним сервис поднялся бы, а тесты всё равно скипались.
|
||||
POSTGRES_DB: tradein
|
||||
POSTGRES_USER: tradein
|
||||
POSTGRES_PASSWORD: tradein
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U tradein"
|
||||
--health-interval 5s
|
||||
--health-timeout 5s
|
||||
--health-retries 10
|
||||
# девять реальных проверок больше. Накладные — только подъём контейнера и
|
||||
# bootstrap схемы (219 файлов, ~20с).
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./tradein-mvp/backend
|
||||
env:
|
||||
DATABASE_URL: postgresql+psycopg://tradein:tradein@localhost:5432/tradein
|
||||
# Имя контейнера уникально на прогон: параллельные PR не дерутся за него.
|
||||
CI_PG: ci-pg-tradein-${{ github.run_id }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Поднять Postgres для тестов
|
||||
working-directory: .
|
||||
# ПОЧЕМУ НЕ `services:` И ПОЧЕМУ БЕЗ ПУБЛИКАЦИИ ПОРТА.
|
||||
# Раннер запускает и job, и сервис-контейнеры с `--network host` (видно в
|
||||
# логе прогона: `docker create image=... network="host"`), а на этом же
|
||||
# хосте на 5432 уже слушает ПРОДОВЫЙ Postgres. Первая попытка через
|
||||
# `services:` + `ports: 5432:5432` (прогон #6853) кончилась тем, что
|
||||
# сервис-контейнер не смог занять порт, а psql из job'а ушёл В ПРОД и
|
||||
# получил `password authentication failed for user "tradein"`. То есть на
|
||||
# этом раннере `localhost:5432` — это боевая база, а не тестовая.
|
||||
#
|
||||
# Поэтому: контейнер поднимаем сами, в дефолтной bridge-сети, БЕЗ
|
||||
# публикации порта, и ходим по его собственному IP. Ноль шансов задеть
|
||||
# прод, ноль конфликтов между параллельными прогонами и никакого
|
||||
# postgresql-client на раннере — psql берём из самого контейнера.
|
||||
#
|
||||
# postgis, не plain postgres: tests/tasks/test_cadastral_geo_match.py
|
||||
# проверяет KNN по geometry (PostGIS_Version() в connectivity-probe).
|
||||
#
|
||||
# Имя БД ОБЯЗАНО отличаться от `test`: `_live_session()` считает DSN с
|
||||
# `localhost:5432/test` заглушкой и возвращает None — иначе контейнер
|
||||
# поднялся бы, а тесты всё равно скипались. Пароль — одноразовый,
|
||||
# CI-локальный, контейнер живёт минуты и наружу не смотрит.
|
||||
run: |
|
||||
docker rm -f "$CI_PG" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$CI_PG" \
|
||||
-e POSTGRES_DB=tradein -e POSTGRES_USER=tradein -e POSTGRES_PASSWORD=tradein \
|
||||
postgis/postgis:16-3.4
|
||||
for _ in $(seq 1 30); do
|
||||
docker exec "$CI_PG" pg_isready -U tradein -q && break
|
||||
sleep 2
|
||||
done
|
||||
docker exec "$CI_PG" pg_isready -U tradein \
|
||||
|| { echo "::error::Postgres не поднялся за 60с"; docker logs "$CI_PG"; exit 1; }
|
||||
ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CI_PG")
|
||||
[ -n "$ip" ] || { echo "::error::не удалось узнать IP контейнера $CI_PG"; exit 1; }
|
||||
echo "DATABASE_URL=postgresql+psycopg://tradein:tradein@${ip}:5432/tradein" >> "$GITHUB_ENV"
|
||||
echo "✓ Postgres на ${ip}:5432 (контейнер $CI_PG)"
|
||||
|
||||
- name: Bootstrap схемы tradein (backend/data/sql/*.sql)
|
||||
working-directory: ./tradein-mvp
|
||||
env:
|
||||
PGPASSWORD: tradein
|
||||
# Тот же порядок и тот же строгий режим, что в deploy-tradein.yml:
|
||||
# `ls | sort` + ON_ERROR_STOP=on, падение любой миграции → job RED.
|
||||
# Никаких «применилось как получилось»: схема в CI либо та же, что на
|
||||
# проде, либо гейта нет.
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y postgresql-client
|
||||
psql -h localhost -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 pg_trgm;
|
||||
CREATE ROLE gendesign_reader;"
|
||||
|
|
@ -130,10 +146,10 @@ jobs:
|
|||
echo "⚠ пропускаю $fname — postgres_fdw к БД gendesign, которой в CI нет"
|
||||
continue
|
||||
fi
|
||||
psql -h localhost -U tradein -d tradein -v ON_ERROR_STOP=on -q -f "$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; }
|
||||
done
|
||||
echo "✓ схема собрана: $(psql -h localhost -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'") таблиц"
|
||||
|
||||
- name: Install uv
|
||||
|
|
@ -188,6 +204,13 @@ jobs:
|
|||
# называет себя вслух, со временем перестаёт быть верным.
|
||||
run: uv run pytest -q -rs
|
||||
|
||||
- name: Снести тестовый Postgres
|
||||
# if: always() — контейнер уходит и когда сьют красный, и когда прогон
|
||||
# отменён concurrency-группой. Иначе на раннере копятся мёртвые контейнеры.
|
||||
if: always()
|
||||
working-directory: .
|
||||
run: docker rm -f "$CI_PG" >/dev/null 2>&1 || true
|
||||
|
||||
# Тесты браузерного сайдкара (#2722). До этого job'а они не бежали НИГДЕ:
|
||||
# ci-tradein гейтил только backend/frontend, deploy-tradein — тоже, а каталог
|
||||
# вне uv-воркспейса, так что и `uv run pytest` из backend их не собирал. Итог:
|
||||
|
|
|
|||
|
|
@ -81,25 +81,8 @@ jobs:
|
|||
#
|
||||
# TEST_DATABASE_URL НАМЕРЕННО НЕ задаётся: на него завязан tests/integration/
|
||||
# (phantom-column gate), которому нужна КОПИЯ ПРОДОВОЙ схемы через pg_dump по
|
||||
# SSH-туннелю. Пустой сервис-контейнер дал бы там красноту на пустом месте,
|
||||
# поэтому integration остаётся честно пропущенным — с причиной в логе (`-rs`).
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
env:
|
||||
# CI-локальная одноразовая БД, не секрет (тот же паттерн, что в
|
||||
# .github/workflows/ci.yml). Имя БД ≠ `test`, чтобы DSN не выглядел
|
||||
# заглушкой для probe-ов в тестах.
|
||||
POSTGRES_DB: gendesign_ci
|
||||
POSTGRES_USER: gendesign
|
||||
POSTGRES_PASSWORD: gendesign
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U gendesign"
|
||||
--health-interval 5s
|
||||
--health-timeout 5s
|
||||
--health-retries 10
|
||||
# SSH-туннелю. Пустой контейнер дал бы там красноту на пустом месте, поэтому
|
||||
# integration остаётся честно пропущенным — с причиной в логе (`-rs`).
|
||||
defaults:
|
||||
run:
|
||||
working-directory: backend
|
||||
|
|
@ -107,11 +90,39 @@ jobs:
|
|||
# TESTING=1 активирует RBAC-bypass (app/main.py rbac_guard пропускает
|
||||
# запросы при settings.testing=True) — иначе 401 на всём /api/v1.
|
||||
TESTING: "1"
|
||||
DATABASE_URL: postgresql+psycopg://gendesign:gendesign@localhost:5432/gendesign_ci
|
||||
REDIS_URL: redis://localhost:6379/0
|
||||
# Имя контейнера уникально на прогон: параллельные PR не дерутся за него.
|
||||
CI_PG: ci-pg-backend-${{ github.run_id }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Поднять Postgres для тестов
|
||||
working-directory: .
|
||||
# ПОЧЕМУ НЕ `services:` И ПОЧЕМУ БЕЗ ПУБЛИКАЦИИ ПОРТА — см. подробный
|
||||
# разбор в ci-tradein.yml (тот же раннер). Кратко: job и сервис-контейнеры
|
||||
# запускаются с `--network host`, а на 5432 этого хоста слушает ПРОДОВЫЙ
|
||||
# Postgres, так что `localhost:5432` из job'а — боевая база. Поднимаем
|
||||
# контейнер сами, в bridge-сети, без публикации порта, ходим по его IP.
|
||||
#
|
||||
# plain postgres:16, БЕЗ PostGIS: тесты tests/sql/ строят себе временные
|
||||
# таблицы и не трогают ни geometry, ни реальную схему — bootstrap схемы
|
||||
# здесь не нужен вовсе, в отличие от tradein-лэйна.
|
||||
run: |
|
||||
docker rm -f "$CI_PG" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$CI_PG" \
|
||||
-e POSTGRES_DB=gendesign_ci -e POSTGRES_USER=gendesign -e POSTGRES_PASSWORD=gendesign \
|
||||
postgres:16
|
||||
for _ in $(seq 1 30); do
|
||||
docker exec "$CI_PG" pg_isready -U gendesign -q && break
|
||||
sleep 2
|
||||
done
|
||||
docker exec "$CI_PG" pg_isready -U gendesign \
|
||||
|| { echo "::error::Postgres не поднялся за 60с"; docker logs "$CI_PG"; exit 1; }
|
||||
ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CI_PG")
|
||||
[ -n "$ip" ] || { echo "::error::не удалось узнать IP контейнера $CI_PG"; exit 1; }
|
||||
echo "DATABASE_URL=postgresql+psycopg://gendesign:gendesign@${ip}:5432/gendesign_ci" >> "$GITHUB_ENV"
|
||||
echo "✓ Postgres на ${ip}:5432 (контейнер $CI_PG)"
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
|
|
@ -164,7 +175,7 @@ jobs:
|
|||
# но --ignore — belt-and-suspenders на случай сбора фикстур).
|
||||
# tests/integration self-skip'ается через requires_test_db (skipif на
|
||||
# TEST_DATABASE_URL, который тут не задан) → НЕ игнорим, оно чисто skip'ается.
|
||||
# tests/sql/ теперь РЕАЛЬНО ИДУТ — postgres-сервис выше (#2745).
|
||||
# tests/sql/ теперь РЕАЛЬНО ИДУТ — postgres-контейнер выше (#2745).
|
||||
# PDF-тесты ИДУТ (libpango выше). Target: 0 failed, skips OK.
|
||||
#
|
||||
# `-rs` (#2745): каждый оставшийся пропуск печатает причину. Под `-q` без
|
||||
|
|
@ -200,6 +211,13 @@ jobs:
|
|||
echo "$report"
|
||||
fi
|
||||
|
||||
- name: Снести тестовый Postgres
|
||||
# if: always() — контейнер уходит и когда сьют красный, и когда прогон
|
||||
# отменён concurrency-группой. Иначе на раннере копятся мёртвые контейнеры.
|
||||
if: always()
|
||||
working-directory: .
|
||||
run: docker rm -f "$CI_PG" >/dev/null 2>&1 || true
|
||||
|
||||
frontend-tests:
|
||||
runs-on: ubuntu-latest
|
||||
needs: changes
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue