Миграция с Hasura v2 на Provisa¶
Предварительные требования¶
- Работающий экземпляр Hasura v2 (v2.x) с экспортированными метаданными.
- Экспортируйте метаданные в любой из двух раскладок:
Каталог метаданных CLI:
Это создаёт каталог metadata/. databases/databases.yaml перечисляет каждый источник с его видом
и подключением и указывает на его таблицы и функции через индексы !include;
actions.yaml, cron_triggers.yaml, inherited_roles.yaml, remote_schemas.yaml и остальные
файлы лежат рядом. Прежняя плоская раскладка (tables.yaml в корне)
тоже преобразуется.
Единый документ API метаданных:
curl -X POST http://localhost:8080/v1/metadata \
-H "X-Hasura-Admin-Secret: <secret>" \
-d '{"type":"export_metadata","args":{}}' > metadata.json
Работают и голый объект, и конверт {resource_version, metadata}. Административный импорт
(Admin, Import Hasura Config) принимает эту форму; консольный конвертер ниже читает
каталоги.
3. Python 3.11+ с установленным пакетом provisa.
Использование CLI¶
Аргументы¶
| Аргумент | Обязателен | Описание |
|---|---|---|
metadata_dir |
Да | Путь к экспортированному каталогу метаданных Hasura v2 |
Опции¶
| Опция | По умолчанию | Описание |
|---|---|---|
-o, --output FILE |
stdout | Путь к выходному файлу YAML |
--source-overrides FILE |
None | Файл YAML с переопределениями подключения по источникам |
--domain-map KEY=VAL ... |
None | Отображения схема-в-домен (например, public=core hr=people) |
--auth-env-file FILE |
None | Путь к файлу .env с конфигурацией авторизации JWT/admin-secret |
--dry-run |
off | Разбор и валидация без записи вывода |
Файл переопределений источников¶
Файл YAML, ключами которого являются имена источников, со свойствами подключения для переопределения:
default:
host: prod-db.example.com
port: 5432
database: myapp
username: provisa_user
password: "${env:PROD_DB_PASSWORD}"
Файл окружения авторизации¶
Файл в стиле .env, содержащий конфигурацию авторизации Hasura для конвертации. Конвертер
отображает:
- JWT с
jwk_url-> Provisaprovider: oauth. - JWT
claims_map-> Provisarole_mapping[]. - Admin secret -> Provisa
superuser. - Авторизация через webhook -> выдаётся предупреждение (нет эквивалента в Provisa).
Отчёт¶
Преобразование заканчивается отчётом из трёх частей:
- Перенесено как есть. У объекта есть аналог в Provisa, и он преобразуется без изменения смысла.
- Сопоставлено с лучшей функцией Provisa. Каждая запись объясняет, почему функция Provisa решает задачу лучше.
- Переделать вручную. Каждая запись говорит, что делать. Это предупреждения.
Консольный конвертер выводит отчёт в stderr как Carried as, Mapped to и Redo by hand, каждую часть со счётчиком, когда что-то осталось переделать. Административный импорт возвращает три списка вместе с предпросмотром.
Таблица соответствия возможностей¶
| Возможность Hasura v2 | Часть | Эквивалент в Provisa | Примечания |
|---|---|---|---|
| Источники (postgres, mysql, mssql, bigquery, citus) | перенесено | sources[] |
Вид сопоставлен: pg/postgres -> postgresql, mssql -> sqlserver. Явно записанный URL подключения разбирается на хост/порт/базу данных/пользователя/пароль. Размер пула сохраняется. URL, читаемый из переменной окружения, не угадывается (см. ниже). |
| Таблицы (отслеживаемые таблицы) | перенесено | tables[] |
Схема и имя таблицы сохраняются. source_id связывает с источником. |
Пользовательские имена таблиц (custom_name, custom_root_fields.select) |
перенесено | tables[].alias |
Первое не-null значение из select, select_by_pk, custom_name. |
Пользовательские имена колонок (column_config, прежний custom_column_names) |
перенесено | columns[].alias |
Пользовательское имя каждой колонки становится её алиасом. |
Прочие имена корневых полей (select_by_pk, select_aggregate, insert, ...) |
сопоставлено | Единственное поле таблицы | Сообщаются по имени. Чтение по ключу — фильтр по таблице, агрегат — её поле _aggregate, запись — её мутация. |
| Разрешения select (колонки, фильтр) | перенесено | columns[].visible_to[], rls_rules[] |
Списки колонок становятся visible_to; * поддерживается. Фильтры становятся SQL; переменные сеанса становятся current_setting('provisa.<name>'). |
| Фильтры через relationships | перенесено | Подзапросы EXISTS в rls_rules[] |
Правило идёт по relationship до её таблицы. |
| Разрешения insert, update и delete | перенесено | tables[].write_grants[] |
Одно разрешение на роль и операцию: колонки из columns, rows из filter, check из check, пресеты из set. Роли получают право write. |
| Роль admin | перенесено | org_admin |
Держит все разрешения на все колонки и строки и названа в каждой импортированной колонке. |
| Object-relationships | перенесено | relationships[] с cardinality: many-to-one |
Одобрены. Поле GraphQL — имя relationship из Hasura. |
| Array-relationships | перенесено | relationships[] с cardinality: one-to-many |
Одобрены. Поле GraphQL — имя relationship из Hasura. |
| Унаследованные роли (одна роль) | перенесено | roles[].parent_role_id |
Роль становится родителем. |
| Унаследованные роли (несколько ролей) | сопоставлено | Роль, держащая объединение их разрешений | У ролей Provisa один родитель, поэтому объединение выписывается на самой роли. |
| Computed fields | переделать | Колонка представления | Экспорт называет функцию, но не её тело. Отчёт даёт SELECT, который нужно добавить. |
| Отслеживаемые функции | перенесено, иначе переделать | Команды functions[] |
Предпросмотр читает сигнатуру из базы данных. Без неё добавьте функцию на странице Commands. |
| Actions | сопоставлено | Команды, вызывающие обработчик | {{VAR}} в URL обработчика записывается как ${env:VAR} и разрешается при вызове команды. |
| Cron-триггеры | перенесено | scheduled_triggers[] |
Cron-выражение, URL, payload и флаг enabled. Повторные попытки не переносятся (предупреждение). |
| Event-триггеры | сопоставлено | scheduled_triggers[] на изменение таблицы |
Срабатывает на пакет изменений при любом изменении. Повторные попытки не переносятся (предупреждение). |
| Remote schemas | сопоставлено | sources[] (graphql_remote), попавший как таблицы |
Имя, URL, заголовки и аутентификация сохраняются; корневые поля становятся управляемыми таблицами. |
| Enum-таблицы | перенесено | Таблица создана | Флаг is_enum не переносится (эквивалента в Provisa нет). |
| Allow lists, query collections, REST-эндпоинты | переделать | Нет | Сообщаются, если присутствуют в сводном экспорте; не преобразуются. |
Relationships¶
- Поле GraphQL каждой relationship — имя из Hasura. Если у таблицы уже есть поле с таким именем, relationship получает имя, которое даёт Provisa, и отчёт предупреждает.
- Две relationship, объявляющие один и тот же join, сливаются в одну. Отчёт сообщает, какая в какую влилась.
- Relationship, соединяющая по нескольким парам колонок, получает предупреждение и не создаётся, так как связь Provisa соединяет по одной паре. Правила строк через неё всё равно преобразуются.
- Object-relationship, объявленная только колонкой внешнего ключа, разрешается через обратную array-relationship; без неё выдаётся предупреждение.
Разрешения на запись¶
Разрешение указывает, что роль может записать в таблицу одной операцией. Пустой filter или check означает любую строку (TRUE). Значение set X-Hasura-User-Id становится пресетом session переменной hasura_user_id; любое другое значение становится пресетом literal. Колонка с пресетом не входит также в колонки разрешения.
Actions, отслеживаемые функции и триггеры¶
- Action становится командой, вызывающей её обработчик, видимой ролям, которые называют её разрешения (только
org_admin, если они не называют ни одной). Асинхронная action получает предупреждение: команда ждёт ответа обработчика. - Отслеживаемая функция становится командой, когда предпросмотр может прочитать её аргументы и возвращаемую таблицу из источника PostgreSQL. Без подключения, а также для любой функции, которой нет, которая перегружена или возвращает таблицу, не зарегистрированную импортом, она переходит в «переделать».
- Event-триггер срабатывает при каждом изменении своей таблицы, один раз на пакет, и отправляет таблицу и изменённые строки, а не событие на строку со старыми и новыми значениями. Триггер, слушавший меньше трёх операций, получает предупреждение.
Шаги после преобразования¶
- Просмотрите отчёт. Начните с
Redo by hand. - Настройте подключения источников. Подключение, которое экспорт записывает явно, преобразуется как есть. Подключение, читаемое из переменной окружения, приходит пустым. Используйте
--source-overridesили отредактируйте результат напрямую. - Проверьте назначение доменов. Без
--domain-mapвсе таблицы попадают вdefault. Назначьте схемам домены с помощью--domain-map public=core analytics=reporting. - Проверьте правила строк. Фильтры преобразуются в SQL. Просмотрите сложные булевы
выражения (вложенные
_and/_or/_exists) и фильтры через relationships. - Переделайте то, что перечислено. Computed fields, отслеживаемые функции, которые предпросмотр не смог прочитать, повторные попытки триггеров и аутентификация через webhook не имеют автоматического преобразования.
- Настройте аутентификацию. Если ваш экземпляр Hasura использует аутентификацию JWT/webhook, создайте файл
окружения аутентификации и запустите снова с
--auth-env-file. - Передайте переменные сеанса. Настройте поставщика идентичности так, чтобы токен нёс каждую переменную сеанса, которую отчёт перечисляет в «сопоставлено с» (например
x-hasura-user-id, на верхнем уровне или внутри объекта claims Hasura), либо запишите её для каждого участника как атрибут. - Проверьте. Запустите сервер Provisa и проверьте запросы к вашим источникам данных.
Распространённые проблемы и устранение неполадок¶
Подключение берётся из переменной окружения¶
Если database_url источника — ссылка на переменную окружения ({"from_env": "PG_URL"}),
её значения в экспорте нет. Конвертер его не угадывает: у источника пустые хост,
база данных, пользователь и пароль, а отчёт перечисляет его в Redo by hand.
Введите подключение в настройках источников импорта (или через --source-overrides) и преобразуйте
снова.
Столбцы-подстановки¶
Когда разрешение предоставляет columns: "*", конвертер создаёт единую запись столбца-подстановки.
После конвертации вы можете захотеть заменить её явными списками столбцов, проверив
фактическую схему базы данных.
Точность event-триггеров¶
Event-триггер становится триггером на изменения своей таблицы, вызывающим тот же webhook. Он срабатывает на пакет изменений при любом изменении — пришло оно через Provisa или напрямую в базу данных — и отправляет таблицу и изменённые строки. Старые и новые значения по строкам, фильтрация по операциям и повторные попытки Hasura не переносятся; отчёт предупреждает о каждом применимом. Пусть обработчик действует на те строки, для которых он предназначен, и повторяет попытки внутри себя, если важен пропущенный вызов.
Отсутствующие роли¶
Роли собираются из разрешений таблиц, разрешений actions и унаследованных ролей. Если роль существует в Hasura, но не встречается ни в одном из них, в результате её не будет.
Пользовательские корневые поля¶
Имена корневых полей select и select_by_pk дают алиас таблицы. Остальные пользовательские корневые поля
(select_aggregate, insert, update, delete) сообщаются по имени: Provisa отдаёт
таблицу одним полем, а агрегаты и записи — как её поле _aggregate и её мутации.
Пример¶
Конвертация типичного проекта Hasura v2 с двумя схемами, отображёнными на домены:
# Export metadata from Hasura
hasura metadata export --endpoint http://localhost:8080
# Convert with domain mapping and source overrides
python -m provisa.hasura_v2 metadata/ \
-o provisa.yaml \
--domain-map public=core hr=people \
--source-overrides overrides.yaml \
--auth-env-file auth.env
# Dry run first to check for warnings
python -m provisa.hasura_v2 metadata/ --dry-run
Структура результата (сокращённо):
sources:
- id: default
type: postgresql
host: prod-db.example.com
port: 5432
database: myapp
domains:
- id: core
- id: people
tables:
- source_id: default
domain_id: core
schema_name: public
table_name: users
columns:
- name: id
visible_to: [org_admin, user]
- name: email
visible_to: [org_admin, user]
write_grants:
- role: user
operation: update
columns: [email]
rows: "id = current_setting('provisa.hasura_user_id')"
check: "TRUE"
- role: org_admin
operation: insert
columns: [id, email]
check: "TRUE"
- role: org_admin
operation: update
columns: [id, email]
rows: "TRUE"
check: "TRUE"
- role: org_admin
operation: delete
rows: "TRUE"
roles:
- id: user
capabilities: [query_development, write]
domain_access: ["*"]
rls_rules:
- table_id: users
role_id: user
filter: "id = current_setting('provisa.hasura_user_id')"
relationships:
- id: orders.user
source_table_id: orders
target_table_id: users
source_column: user_id
target_column: id
cardinality: many-to-one
graphql_alias: user