|
| 1 | +# DEV migration connection в CI |
| 2 | + |
| 3 | +## Задача и границы |
| 4 | + |
| 5 | +Deploy Dev применял миграции под runtime login из `.env`, что приводило к |
| 6 | +`must be owner of table`. Теперь отдельные credentials используются только внутри |
| 7 | +одноразового migration container. Web/Celery остаются под `procollab_api_dev`. |
| 8 | +API, UI, модели, numbered migrations, PROD workflow и production secrets не меняются. |
| 9 | + |
| 10 | +Под migration-role здесь понимается существующий владелец `procollab_api` в отдельном |
| 11 | +CI-подключении. Новый PostgreSQL login не создаётся. Одноразовая подготовка ниже |
| 12 | +использует временное членство DEV role → migration role с последующим отзывом. |
| 13 | +CI жёстко ограничивает database name и runtime user; secrets PROD workflow не читаются. |
| 14 | + |
| 15 | +## Согласованная схема DEV / PROD, 18.10.2026 |
| 16 | + |
| 17 | +Пользователь выбрал `procollab_api` для миграций обоих контуров, сохранив |
| 18 | +существующие подключения web/Celery. [Read-only preflight обоих контуров](https://github.com/PROCOLLAB-github/api/actions/runs/39072746180) |
| 19 | +проверил actual connection identity, ownership и role flags в READ ONLY transaction: |
| 20 | + |
| 21 | +| Контур | Фактическая БД | Web/Celery сейчас | Миграции сейчас | Целевая роль миграций | |
| 22 | +| --- | --- | --- | --- | --- | |
| 23 | +| DEV | `procollab_db_dev` | `procollab_api_dev` | `procollab_api_dev` | `procollab_api` | |
| 24 | +| PROD | `procollab_db_test` | `procollab_api` | `procollab_api` | `procollab_api` — уже используется | |
| 25 | + |
| 26 | +Название `procollab_db_test` здесь относится к фактическому deployed PROD: |
| 27 | +контур установлен по серверному checkout и работающему production image, а не по имени БД. |
| 28 | +Одинаковый `pg_control_system().system_identifier` подтвердил общий PostgreSQL-кластер. |
| 29 | +Следовательно, `procollab_api` — одна существующая cluster-wide роль, а не два |
| 30 | +независимых login с одинаковым названием. Её пароль не ротируется в рамках задачи. |
| 31 | + |
| 32 | +В PROD все 107 tables / 100 sequences принадлежат `procollab_api`; не принадлежащих |
| 33 | +ей relations нет. Для выбранной роли изменения PROD workflow, ownership и grants |
| 34 | +не требуются. Существующий PROD runtime тоже использует эту роль; разделение |
| 35 | +runtime/migration прав в PROD не заявляется и в scope не входит. |
| 36 | + |
| 37 | +В DEV остаётся одноразовая подготовка university table/sequence, описанная ниже. |
| 38 | +Секрет `DEV_MIGRATION_DATABASE_PASSWORD` добавлен пользователем; наличие подтверждено |
| 39 | +по metadata, значение не читалось и live owner authentication пока не проверена. |
| 40 | + |
| 41 | +## Фактическое состояние до активации, 17.10.2026 |
| 42 | + |
| 43 | +[Read-only inspection](https://github.com/PROCOLLAB-github/api/actions/runs/39059401759): |
| 44 | + |
| 45 | +- Runtime image: `c78078c26acb6585867b0b5c1a9be93abd76d45b-5ac944d0794f`. |
| 46 | +- PostgreSQL 14.24; database `procollab_db_dev`; runtime `procollab_api_dev`. |
| 47 | +- 95 tables / 89 sequences принадлежат `procollab_api`. |
| 48 | +- `users_university` / `users_university_id_seq` принадлежат runtime login. |
| 49 | +- Оба login не имеют SUPERUSER / CREATEDB / CREATEROLE / REPLICATION / BYPASSRLS. |
| 50 | +- У владельца уже есть default ACL для runtime на новые tables/sequences/functions. |
| 51 | +- Отдельных DEV migration secrets до задачи не было. |
| 52 | + |
| 53 | +## Проверяемые требования |
| 54 | + |
| 55 | +| AC | Наблюдаемый результат | |
| 56 | +| --- | --- | |
| 57 | +| M1 | Только refs/heads/dev; database строго procollab_db_dev | |
| 58 | +| M2 | Missing secret отклоняется до SSH, без fallback на application/production secrets | |
| 59 | +| M3 | Migration credentials не записываются в .env, CLI argv, web/Celery environment | |
| 60 | +| M4 | Неадминистративная migration-role может изменять существующие DEV relations | |
| 61 | +| M5 | Новые tables/sequences доступны runtime для штатных CRUD/sequence операций | |
| 62 | +| M6 | Mixed ownership отклоняется до DDL/default-privilege изменений | |
| 63 | +| M7 | После migrate обычное подключение проходит migrate --check | |
| 64 | +| M8 | Конкурирующие DEV deploy выполняются последовательно; PROD не затронут | |
| 65 | + |
| 66 | +## CI secrets и последовательность |
| 67 | + |
| 68 | +GitHub Environment `dev`, deployment branch policy — только branch `dev`, без |
| 69 | +manual reviewers. В нём используются отдельные secrets: |
| 70 | + |
| 71 | +- `DEV_MIGRATION_DATABASE_USER`: `procollab_api`. |
| 72 | +- `DEV_MIGRATION_DATABASE_PASSWORD`: актуальный пароль этого login из Selectel. |
| 73 | + |
| 74 | +Environment variable `DEV_SSH_HOST_KEY_SHA256` содержит публичный ED25519 fingerprint |
| 75 | +DEV-сервера, проверенный отдельной read-only inspection. Это не пароль. При ротации |
| 76 | +host key сначала независимо проверяется новый публичный ключ, затем обновляется |
| 77 | +эта переменная; отключать host verification нельзя. |
| 78 | + |
| 79 | +Пароль добавляется владельцем через GitHub Environment secrets; в чат, issue, PR |
| 80 | +или repository file его передавать нельзя. GitHub API не позволяет прочитать |
| 81 | +ранее сохранённое значение секрета. Обычные DEV_DATABASE_* остаются без изменения. |
| 82 | + |
| 83 | +Перед SSH runner проверяет credentials и создаёт base64 JSON, регистрирует его |
| 84 | +через [add-mask](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#masking-a-value-in-a-log) |
| 85 | +и передаёт как step output в stdin helper. Base64 обеспечивает только безопасный |
| 86 | +transport, не шифрование. В payload нет host/database overrides. |
| 87 | + |
| 88 | +`scripts/dev_ci_ssh.cjs` использует pinned `ssh2@1.16.0`, проверяет host fingerprint |
| 89 | +и запускает только фиксированный `bash -se`. Весь deploy script передаётся через |
| 90 | +SSH stdin: secret-bearing script не попадает в argv удалённого shell и не |
| 91 | +записывается во временный файл. Старый SSH action заменён только в DEV workflow. |
| 92 | + |
| 93 | +Helper проверяет фактическую identity, schema CREATE/USAGE, отсутствие admin flags, |
| 94 | +ownership tables/sequences и текущие runtime grants внутри READ ONLY transaction. |
| 95 | +Затем настраивает additive default privileges, выполняет migrate, проверяет отсутствие |
| 96 | +pending migrations и доступ runtime к relations. Отдельный container выполняет |
| 97 | +`manage.py migrate --check` с обычной `.env` до перезапуска web/Celery. |
| 98 | + |
| 99 | +## Одноразовая подготовка ownership |
| 100 | + |
| 101 | +До merge/активации нужно выполнить [dev-migration-role-university-owner.sql](dev-migration-role-university-owner.sql) |
| 102 | +только в `procollab_db_dev`. Транзакция переносит ровно university table и проверяет |
| 103 | +её owned sequence, сохраняя runtime privileges и данные. Это не повторяемый шаг CI. |
| 104 | + |
| 105 | +На фактической PostgreSQL 14 отдельный администратор не обязателен: |
| 106 | +[GRANT PostgreSQL 14](https://www.postgresql.org/docs/14/sql-grant.html) разрешает |
| 107 | +session_user выдавать членство в собственной роли. Это отдельно проверено на |
| 108 | +[disposable PostgreSQL 14.24](https://github.com/PROCOLLAB-github/api/actions/runs/39075480886). |
| 109 | +Поведение PostgreSQL 18 отличается; helper отказывается применять эту процедуру к другим major versions. |
| 110 | + |
| 111 | +`scripts/dev_ci_prepare_owner.py` использует два существующих login: |
| 112 | + |
| 113 | +1. Спрашивает только скрытый пароль `procollab_api`; runtime credentials берёт из environment контейнера. |
| 114 | +2. Подключается к DEV под `procollab_api_dev` и `procollab_api`, проверяет обе identity и PostgreSQL 14. |
| 115 | +3. При необходимости временно делает `GRANT procollab_api_dev TO procollab_api`. |
| 116 | +4. Под `procollab_api` выполняет проверенный SQL смены владельца и runtime grants. |
| 117 | +5. В `finally` отзывает только добавленное членство; существующее членство не удаляет. |
| 118 | + |
| 119 | +Направление grant — DEV role → migration role. Runtime DEV не становится членом |
| 120 | +PROD owner-role. Членство действует на кластер, поэтому временно расширяются права |
| 121 | +общей `procollab_api` на DEV; PROD objects и grants не меняются. Advisory lock не |
| 122 | +позволяет двум экземплярам helper конкурировать. Ошибки SQL откатывают незавершённую |
| 123 | +транзакцию перед отзывом членства; SIGINT/SIGTERM/SIGHUP выполняют cleanup. |
| 124 | +При принудительном убийстве процесса или недоступности БД cleanup нельзя гарантировать: |
| 125 | +перед продолжением проверить членство и при необходимости под `procollab_api_dev` |
| 126 | +выполнить `REVOKE procollab_api_dev FROM procollab_api`. |
| 127 | + |
| 128 | +Для самого SQL нужны права фактического владельца и возможность передать ownership целевой роли; |
| 129 | +server root или обычного GRANT ALL недостаточно для этого |
| 130 | +([PostgreSQL 14 ALTER TABLE](https://www.postgresql.org/docs/14/sql-altertable.html)). |
| 131 | +При недостатке прав или неожиданной identity транзакция откатится целиком. |
| 132 | +Mass REASSIGN OWNED не используется: он затрагивает также shared database/tablespace |
| 133 | +objects ([PostgreSQL](https://www.postgresql.org/docs/16/role-removal.html)). |
| 134 | + |
| 135 | +Нельзя мержить fail-closed workflow до добавления пароля и нормализации ownership: |
| 136 | +иначе следующий DEV deploy ожидаемо остановится. Старая версия приложения при |
| 137 | +ошибке preflight остаётся в работающих контейнерах. |
| 138 | + |
| 139 | +## Проверка |
| 140 | + |
| 141 | +`python -m unittest discover -s scripts/tests -p test_dev_ci_migrate.py -v` запускает |
| 142 | +credentials tests. Реальные PostgreSQL tests требуют disposable instance и |
| 143 | +`RUN_DEV_MIGRATION_PG_TESTS=1`; они отказываются заменять существующую тестовую БД |
| 144 | +или роли. Матрица GitHub CI покрывает PostgreSQL 14 (версия DEV) и 18. |
| 145 | + |
| 146 | +`node --test scripts/tests/test_dev_ci_ssh.cjs` с установленным `ssh2@1.16.0` |
| 147 | +проверяет реальное loopback SSH соединение: stdin вместо argv, отказ чужому host |
| 148 | +key до authentication, missing config, controlled remote failure и timeout. |
| 149 | + |
| 150 | +Проверяются исходный ownership failure, успешные ALTER/CREATE migrations, runtime |
| 151 | +CRUD/sequence на новой таблице, сохранность `.env`, idempotency, READ ONLY check, |
| 152 | +mixed ownership fail-before-write, отказ admin credentials, special characters |
| 153 | +в пароле и отсутствие пароля/traceback в сообщении об ошибке. |
| 154 | + |
| 155 | +Фактические результаты и ограничения активации фиксируются в PR description. |
| 156 | +Без настоящих migration credentials live DDL проверка не объявляется выполненной. |
0 commit comments