Перейти к содержанию

Миграция с Hasura v2 на Provisa

Предварительные требования

  1. Работающий экземпляр Hasura v2 (v2.x) с экспортированными метаданными.
  2. Экспортируйте метаданные в любой из двух раскладок:

Каталог метаданных CLI:

hasura metadata export --endpoint http://localhost:8080

Это создаёт каталог 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

python -m provisa.hasura_v2 <metadata-dir> -o provisa.yaml

Аргументы

Аргумент Обязателен Описание
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 -> Provisa provider: oauth.
  • JWT claims_map -> Provisa role_mapping[].
  • Admin secret -> Provisa superuser.
  • Авторизация через webhook -> выдаётся предупреждение (нет эквивалента в Provisa).

Отчёт

Преобразование заканчивается отчётом из трёх частей:

  1. Перенесено как есть. У объекта есть аналог в Provisa, и он преобразуется без изменения смысла.
  2. Сопоставлено с лучшей функцией Provisa. Каждая запись объясняет, почему функция Provisa решает задачу лучше.
  3. Переделать вручную. Каждая запись говорит, что делать. Это предупреждения.

Консольный конвертер выводит отчёт в 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-триггер срабатывает при каждом изменении своей таблицы, один раз на пакет, и отправляет таблицу и изменённые строки, а не событие на строку со старыми и новыми значениями. Триггер, слушавший меньше трёх операций, получает предупреждение.

Шаги после преобразования

  1. Просмотрите отчёт. Начните с Redo by hand.
  2. Настройте подключения источников. Подключение, которое экспорт записывает явно, преобразуется как есть. Подключение, читаемое из переменной окружения, приходит пустым. Используйте --source-overrides или отредактируйте результат напрямую.
  3. Проверьте назначение доменов. Без --domain-map все таблицы попадают в default. Назначьте схемам домены с помощью --domain-map public=core analytics=reporting.
  4. Проверьте правила строк. Фильтры преобразуются в SQL. Просмотрите сложные булевы выражения (вложенные _and/_or/_exists) и фильтры через relationships.
  5. Переделайте то, что перечислено. Computed fields, отслеживаемые функции, которые предпросмотр не смог прочитать, повторные попытки триггеров и аутентификация через webhook не имеют автоматического преобразования.
  6. Настройте аутентификацию. Если ваш экземпляр Hasura использует аутентификацию JWT/webhook, создайте файл окружения аутентификации и запустите снова с --auth-env-file.
  7. Передайте переменные сеанса. Настройте поставщика идентичности так, чтобы токен нёс каждую переменную сеанса, которую отчёт перечисляет в «сопоставлено с» (например x-hasura-user-id, на верхнем уровне или внутри объекта claims Hasura), либо запишите её для каждого участника как атрибут.
  8. Проверьте. Запустите сервер 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