Перенос версий через API
Эндпоинты переноса версий публичного API v2 выгружают версию проекта в Git-репозиторий или в файл-архив и загружают её обратно, благодаря чему скрипт или CI/CD-конвейер может переносить конфигурацию DataForge между средами без обращения к интерфейсу; вся эта группа эндпоинтов обслуживается по базовому пути /df-api/v2 и требует аутентификации по ключу API.
Общая информация
| Свойство | Значение |
|---|---|
| Базовый путь | /df-api/v2 |
| Аутентификация | Заголовок X-Api-Key (см. страницу Ключи API) |
| Префикс пути | {v} ниже обозначает /projects/{project_id}/versions/{version_id} |
| Требуемый доступ | Аналитик или выше для экспорта и сухих прогонов, разработчик или выше для импорта, администратор компании для Git-подключений |
| Ограничение частоты запросов | 100 запросов за 60 секунд на один ключ |
| Лицензия | Доступен только при действующей лицензии вызывающей компании |
| Аудит | Каждый вызов фиксируется в журнале аудита, для операций записи — со снимками объекта до и после изменения |
Тот же механизм экспорта и импорта доступен в интерфейсе — на странице Экспорт и импорт версий проекта описано, что входит в версию, как устроено дерево файлов и как обрабатываются слитые ветки Git.
Эндпоинты
| Метод | Путь | Требуемый доступ | Описание |
|---|---|---|---|
POST |
{v}/export/git |
Аналитик или выше | Выгрузить версию коммитом в Git-репозиторий |
POST |
{v}/export/file |
Аналитик или выше | Выгрузить версию в архив .dfexport.zip и получить подписанную ссылку на скачивание |
POST |
{v}/import/validate |
Аналитик или выше | Проверить источник импорта, ничего не записывая |
POST |
{v}/import/preview |
Аналитик или выше | Показать, что изменит импорт, ничего не записывая |
POST |
{v}/import/git |
Разработчик или выше | Загрузить конфигурацию из ветки Git-репозитория |
POST |
{v}/import/file |
Разработчик или выше | Загрузить конфигурацию из переданного архива (multipart-запрос) |
Требуемый доступ — это эффективная роль владельца ключа API в проекте. Экспорт и сухие прогоны требуют роли «аналитик» или выше, то есть строже, чем в интерфейсе, где выгрузка доступна и наблюдателю. Если у владельца ключа нет доступа к проекту, ответом будет 404 с кодом DF_API.PROJECT_NOT_FOUND, и существование проекта не раскрывается.
Учётные данные Git
Учётные данные для репозитория передаются одним из двух способов: непосредственно в объекте authentication либо ссылкой connection_id на сохранённое Git-подключение.
| Поле | Метод | Описание |
|---|---|---|
authentication.method |
— | pat, ssh или password |
authentication.token |
pat |
Персональный токен доступа |
authentication.private_key, authentication.passphrase |
ssh |
Приватный SSH-ключ и, при необходимости, парольная фраза к нему |
authentication.username, authentication.password |
password |
Логин и пароль |
Правила, действующие при использовании сохранённого подключения:
- целевой репозиторий всегда задаётся полем
repository_urlзапроса; сохранённое подключение отдаёт только учётные данные - учётные данные подключения принимаются только для хоста этого подключения
- подключение с выключенными
allow_branch_overrideилиallow_path_overrideотклоняет запрос, ветка или путь которого отличаются от сохранённых - отсутствующий
pathозначает путь, сохранённый в подключении
Учётные данные никогда не возвращаются в ответе — токены, пароли и закрытые ключи не входят ни в одну схему ответа, — а учётные данные, вписанные в repository_url, удаляются из ответа, записи аудита и сохранённого подключения.
Экспорт версии проекта
Тело запроса export/git содержит обязательные поля repository_url, branch и commit_message, а также path, connection_id либо authentication и options. Тело запроса export/file содержит только options.
| Опция | По умолчанию | Описание |
|---|---|---|
include_rmd, include_fact_tables, include_data_marts, include_connections |
true |
Какие разделы попадают в выгрузку |
include_history |
false |
Включить историю изменений ячеек РПИ |
encrypt_sensitive |
true |
Значение false отклоняется с ответом 400: пароли подключений никогда не выгружаются в открытом виде |
add_gitattributes |
true |
Только для export/git: положить в дерево файл .gitattributes |
save_connection, connection_name |
false |
Только для export/git: сохранить переданные в authentication учётные данные как новое личное Git-подключение с указанным именем |
encryption_password, use_system_key |
— | Только для export/file: обернуть архив в шифрованный конверт; приоритет имеет пароль |
export/git отвечает полями success, commit_hash, repository_url, branch, path, files_created, timestamp и connection_id. export/file отвечает полями download_url, file_name, file_size и expires_at; ссылка подписана и имеет ограниченный срок жизни.
Экспорт детерминирован — повторная выгрузка неизменённой версии даёт то же дерево; отличается только манифест отметкой времени экспорта, и отличие в одном манифесте считается отсутствием изменений. Поэтому повторный export/git не создаёт коммит и отвечает commit_hash: null, а ветка никогда не перезаписывается: изменения ложатся обычными коммитами поверх её истории.
Проверка и предпросмотр импорта
Оба эндпоинта представляют собой сухие прогоны и ничего не записывают, поэтому именно они служат проверочным шагом конвейера перед развёртыванием.
import/validateпринимает то же тело, что и импорт, но безtarget, плюс обязательное полеsource_typeсо значениемgitилиfile. В ответе —valid, массивыerrors[]иwarnings[](каждая запись содержитlevel, машинныйcode,message,elementиelement_type),summaryсо счётчикамиmeasures,dimensions,facts,fact_tablesиdata_marts, а такжеschema_versionimport/previewсравнивает источник с версией, указанной в пути, и отвечает полямиpreview_id,summaryпо тем же пяти разделам (added,modified,deleted,total),conflicts[]иconflict_resolution_required. В предпросмотреsource_typeможно опустить — тип определяется по самому запросу
Каждая запись conflicts[] описывает одно поле: element_type, element_id, element_name, field, current_value и imported_value.
Git-источник передаётся как JSON, файловый источник — как multipart-запрос с частью file.
Импорт версии
Тело запроса import/git содержит обязательные поля repository_url и branch, а также path, commit_hash (закрепить конкретный коммит вместо вершины ветки), connection_id либо authentication, target, conflict_strategy и options. Тело запроса import/file — multipart: часть file с архивом .dfexport.zip, плоские текстовые поля target.*, conflict_strategy и encryption_password, а также options в виде JSON-строки.
Целевая версия
| Поле | Описание |
|---|---|
target.method |
create (по умолчанию) — создать новую версию с учётом лимита версий по лицензии; replace — полностью заменить версию, указанную в пути |
target.version_name |
Обязательное. Имя новой версии либо новое имя заменяемой версии |
Имя версии подчиняется тем же правилам, что и в интерфейсе: до 64 символов, буквы, цифры, подчёркивание, пробел, точка и дефис. Отсутствие имени даёт ответ 400 с кодом DF_API.MISSING_REQUIRED_FIELD, а имя, занятое другой живой версией проекта, — ответ 409 с кодом duplicate_name.
В обоих режимах версия, указанная в пути, служит базой сравнения для стратегии разрешения конфликтов.
Обработка конфликтов
conflict_strategy действует поэлементно:
| Стратегия | Поведение |
|---|---|
smart_merge (по умолчанию) |
Добавления и изменения из источника применяются; элементы, существующие только в текущей версии, сохраняются |
overwrite |
Источник применяется дословно, включая удаления |
skip |
Конфликтующие элементы сохраняют текущие значения; добавления применяются, удаления — нет |
manual |
При наличии конфликтов ничего не записывается: ответ содержит success: false и список конфликтов |
Опции импорта
| Опция | По умолчанию | Описание |
|---|---|---|
import_rmd, import_fact_tables, import_data_marts, import_connections |
true |
Какие разделы применяются; выключенный раздел остаётся таким, как в текущей версии |
import_history |
false |
Применить историю изменений РПИ, если она есть в источнике |
detect_merges |
true |
Перенести сведения о слиянии, записанные в манифесте источника |
decrypt_sensitive |
true |
При значении false подключения импортируются без паролей и получают статус, требующий обновления |
encryption_password |
— | Пароль запечатанного архива |
Ответ импорта содержит success, import_id, target_project_id, target_version_id, target_version_name (идентификаторы передаются строками), summary фактически применённых изменений по пяти разделам, conflicts[] и conflict_resolution_required.
Повторные запросы
Импорт идемпотентен по заголовку Idempotency-Key, значением которого должен быть UUID версии 4: повторный запрос попадает в ту же версию и возвращает тот же import_id вместо создания второй версии. Используйте стабильный ключ, производный от запуска конвейера, а не случайный на каждую попытку. Эндпоинты export/file, import/validate, import/preview и проверка подключения намеренно не возвращают сохранённый ответ по ключу — закешированный ответ был бы устаревшим, например с протухшей ссылкой или неактуальной проверкой.
Управление Git-подключениями
Реестр сохранённых Git-подключений компании доступен по пути /df-api/v2/git-connections. Все эндпоинты требуют роли администратора компании или суперадминистратора; недоступный ключу ресурс отвечает 404, недостаточная роль — 403.
| Метод | Путь | Описание |
|---|---|---|
GET |
/git-connections |
Список подключений компании; page и pageSize до 100, большее значение отклоняется с кодом page_size_exceeded |
POST |
/git-connections |
Создать подключение (201); перед сохранением подключение проверяется, и недостижимый репозиторий отклоняется |
GET |
/git-connections/{connection_id} |
Получить одно подключение |
PUT |
/git-connections/{connection_id} |
Обновить подключение; отсутствующее authentication сохраняет прежние учётные данные, а смена хоста требует ввести их заново |
DELETE |
/git-connections/{connection_id} |
Удалить подключение (204); версии, импортированные через него, сохраняются и лишь теряют ссылку |
POST |
/git-connections/{connection_id}/test |
Проверить подключение и обновить его сохранённый статус |
Тело создания содержит обязательные поля name, platform, repository_url, branch и authentication, а также необязательные path и settings. Словарь значений, допустимых на запись, ограниченнее того, что может встретиться на чтении:
| Поле | На запись | Дополнительно на чтение |
|---|---|---|
platform |
github, gitlab, bitbucket, azure-devops, generic |
github-enterprise |
authentication.method |
pat, ssh, password |
— (в ответах authentication не возвращается) |
status |
— | active, если проверка пройдена, иначе failed |
| Настройка | По умолчанию | Описание |
|---|---|---|
settings.allow_branch_override |
true |
Разрешить экспорту или импорту через это подключение указывать другую ветку |
settings.allow_path_override |
true |
То же для пути внутри репозитория |
settings.set_as_default |
false |
Сделать подключением компании по умолчанию, сняв признак с прежнего |
settings.share_with_all |
false |
Сделать подключение доступным всем пользователям компании; неразделяемое подключение видят только его создатель и администраторы |
Объект подключения в ответе содержит id, name, platform, repository_url, branch, path, status, last_used (обновляется экспортом и импортом через это подключение), created_at, created_by, shared и default.
Проверка отвечает полями git-connection_id, status (success или failed), tests — пять проверок: repository_reachable, authentication_valid, branch_exists и write_permission образуют лесенку, где каждая следующая подразумевает предыдущие, а path_accessible проверяется отдельно (путь может отсутствовать в репозитории, в который в остальном можно писать), — details (last_commit, last_commit_date, available_branches) и tested_at. Пустой репозиторий является допустимой целью экспорта: проверки проходят, а last_commit равен null.
Использование API в CI/CD-конвейере
Эти эндпоинты позволяют управлять конфигурацией DataForge как кодом: изменения выгружаются в Git, проходят ревью через merge request, проверяются конвейером и разворачиваются по средам. Типичный конвейер состоит из четырёх этапов:
- выгрузить исходную версию через
export/git, чтобы каждое изменение попадало коммитом в репозиторий конфигурации - проверить ветку относительно целевой среды через
import/validateи остановить конвейер, покаvalidравноfalse - загрузить конфигурацию в среду staging через
import/git, обычно сtarget.methodв значенииcreateи именем версии, производным от запуска конвейера - загрузить конфигурацию в среду production тем же вызовом, но с ручным подтверждением
Значения, которые конвейер обычно хранит в переменных:
| Переменная | Описание |
|---|---|
| Базовый URL | Базовый URL публичного API, например https://dataforge.example.com/df-api/v2 |
| Ключ API | Для каждой среды свой ключ, в настройках CI помечается как masked |
| Идентификаторы исходного проекта и версии | Версия, которая выгружается |
| Идентификаторы целевого проекта и версии | Базовая версия каждой целевой среды |
| URL репозитория, ветка и путь | Координаты репозитория конфигурации; не называйте переменную пути PATH, иначе будет затёрт системный путь shell-раннера |
| Учётные данные Git | Токен, ключ или пароль, используемые конвейером; в настройках CI помечаются как masked |
Альтернативный вариант — передавать connection_id вместо authentication, тогда учётные данные Git не покидают DataForge.
Пример GitLab CI
# .gitlab-ci.yml
stages:
- export
- validate
- deploy-staging
- deploy-production
export-config:
stage: export
script:
- |
curl --fail-with-body -X POST "${DATAFORGE_URL}/projects/${PROJECT_ID}/versions/${VERSION_ID}/export/git" \
-H "X-Api-Key: ${DATAFORGE_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"repository_url": "'"${REPO_URL}"'",
"branch": "'"${BRANCH}"'",
"path": "'"${REPO_PATH}"'",
"commit_message": "Export from DataForge, pipeline '"${CI_PIPELINE_ID}"'",
"authentication": { "method": "pat", "token": "'"${GIT_TOKEN}"'" }
}'
only:
- main
validate-config:
stage: validate
script:
- |
RESPONSE=$(curl --fail-with-body -sS -X POST "${DATAFORGE_URL}/projects/${STAGING_PROJECT_ID}/versions/${STAGING_VERSION_ID}/import/validate" \
-H "X-Api-Key: ${STAGING_DATAFORGE_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"source_type": "git",
"repository_url": "'"${REPO_URL}"'",
"branch": "'"${BRANCH}"'",
"path": "'"${REPO_PATH}"'",
"authentication": { "method": "pat", "token": "'"${GIT_TOKEN}"'" }
}')
- echo "${RESPONSE}"
- test "$(echo "${RESPONSE}" | jq -r '.valid')" = "true"
only:
- main
deploy-staging:
stage: deploy-staging
script:
- |
H=$(echo -n "dataforge-${CI_PIPELINE_ID}-staging" | md5sum | cut -c1-32)
IDEMPOTENCY_KEY="${H:0:8}-${H:8:4}-4${H:13:3}-8${H:17:3}-${H:20:12}"
- |
RESPONSE=$(curl --fail-with-body -sS -X POST "${DATAFORGE_URL}/projects/${STAGING_PROJECT_ID}/versions/${STAGING_VERSION_ID}/import/git" \
-H "X-Api-Key: ${STAGING_DATAFORGE_API_KEY}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-d '{
"repository_url": "'"${REPO_URL}"'",
"branch": "'"${BRANCH}"'",
"path": "'"${REPO_PATH}"'",
"target": { "method": "create", "version_name": "staging_'"${CI_PIPELINE_ID}"'" },
"conflict_strategy": "smart_merge",
"authentication": { "method": "pat", "token": "'"${GIT_TOKEN}"'" }
}')
- echo "${RESPONSE}"
- test "$(echo "${RESPONSE}" | jq -r '.success')" = "true"
only:
- main
environment:
name: staging
deploy-production:
stage: deploy-production
script:
- |
H=$(echo -n "dataforge-${CI_PIPELINE_ID}-production" | md5sum | cut -c1-32)
IDEMPOTENCY_KEY="${H:0:8}-${H:8:4}-4${H:13:3}-8${H:17:3}-${H:20:12}"
- |
RESPONSE=$(curl --fail-with-body -sS -X POST "${DATAFORGE_URL}/projects/${PRODUCTION_PROJECT_ID}/versions/${PRODUCTION_VERSION_ID}/import/git" \
-H "X-Api-Key: ${PRODUCTION_DATAFORGE_API_KEY}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-d '{
"repository_url": "'"${REPO_URL}"'",
"branch": "'"${BRANCH}"'",
"path": "'"${REPO_PATH}"'",
"target": { "method": "create", "version_name": "production_'"${CI_PIPELINE_ID}"'" },
"conflict_strategy": "smart_merge",
"authentication": { "method": "pat", "token": "'"${GIT_TOKEN}"'" }
}')
- echo "${RESPONSE}"
- test "$(echo "${RESPONSE}" | jq -r '.success')" = "true"
only:
- main
when: manual
environment:
name: production
Idempotency-Key обязан быть UUID v4; в примере он детерминированно выводится из идентификатора пайплайна, поэтому перезапуск упавшего job'а отправит тот же ключ и попадёт в ту же созданную версию, а не создаст вторую.
Пример GitHub Actions
# .github/workflows/dataforge-deploy.yml
name: DataForge deploy
on:
push:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Validate configuration
run: |
RESPONSE=$(curl --fail-with-body -sS -X POST "${{ vars.DATAFORGE_URL }}/projects/${{ vars.STAGING_PROJECT_ID }}/versions/${{ vars.STAGING_VERSION_ID }}/import/validate" \
-H "X-Api-Key: ${{ secrets.STAGING_DATAFORGE_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{
"source_type": "git",
"repository_url": "${{ vars.REPO_URL }}",
"branch": "main",
"authentication": { "method": "pat", "token": "${{ secrets.GIT_TOKEN }}" }
}')
echo "${RESPONSE}"
test "$(echo "${RESPONSE}" | jq -r '.valid')" = "true"
deploy-staging:
needs: validate
runs-on: ubuntu-latest
environment: staging
steps:
- name: Import into staging
run: |
H=$(echo -n "dataforge-${{ github.run_id }}-staging" | md5sum | cut -c1-32)
IDEMPOTENCY_KEY="${H:0:8}-${H:8:4}-4${H:13:3}-8${H:17:3}-${H:20:12}"
RESPONSE=$(curl --fail-with-body -sS -X POST "${{ vars.DATAFORGE_URL }}/projects/${{ vars.STAGING_PROJECT_ID }}/versions/${{ vars.STAGING_VERSION_ID }}/import/git" \
-H "X-Api-Key: ${{ secrets.STAGING_DATAFORGE_API_KEY }}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-d '{
"repository_url": "${{ vars.REPO_URL }}",
"branch": "main",
"target": { "method": "create", "version_name": "staging_${{ github.run_number }}" },
"conflict_strategy": "smart_merge",
"authentication": { "method": "pat", "token": "${{ secrets.GIT_TOKEN }}" }
}')
echo "${RESPONSE}"
test "$(echo "${RESPONSE}" | jq -r '.success')" = "true"
deploy-production:
needs: deploy-staging
runs-on: ubuntu-latest
environment: production # ручное подтверждение настраивается в среде GitHub
steps:
- name: Import into production
run: |
H=$(echo -n "dataforge-${{ github.run_id }}-production" | md5sum | cut -c1-32)
IDEMPOTENCY_KEY="${H:0:8}-${H:8:4}-4${H:13:3}-8${H:17:3}-${H:20:12}"
RESPONSE=$(curl --fail-with-body -sS -X POST "${{ vars.DATAFORGE_URL }}/projects/${{ vars.PRODUCTION_PROJECT_ID }}/versions/${{ vars.PRODUCTION_VERSION_ID }}/import/git" \
-H "X-Api-Key: ${{ secrets.PRODUCTION_DATAFORGE_API_KEY }}" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ${IDEMPOTENCY_KEY}" \
-d '{
"repository_url": "${{ vars.REPO_URL }}",
"branch": "main",
"target": { "method": "create", "version_name": "production_${{ github.run_number }}" },
"conflict_strategy": "smart_merge",
"authentication": { "method": "pat", "token": "${{ secrets.GIT_TOKEN }}" }
}')
echo "${RESPONSE}"
test "$(echo "${RESPONSE}" | jq -r '.success')" = "true"
Обработка ошибок в конвейере
| Ситуация | Как распознать | Что делать |
|---|---|---|
| Источник невалиден | import/validate отвечает valid: false и перечисляет проблемы в errors[] с машинными кодами, например REQUIRED_FILE_MISSING, DUPLICATE_ID |
Остановить конвейер до развёртывания; предупреждения в warnings[], например REFERENCE_MISSING или CIRCULAR_DEPENDENCY, развёртывание не блокируют |
| При импорте найдены конфликты | Ответ содержит conflict_resolution_required: true и conflicts[] со значениями current_value и imported_value |
При smart_merge, overwrite или skip конфликты уже разрешены выбранной стратегией и приведены в ответе для протокола; при manual импорт ничего не записывает, и конвейер может показать конфликты для решения человека |
| Проверка перед слиянием | import/preview возвращает те же различия без записи |
Полезно в задании merge request: показать summary и conflicts до слияния веток |
| Запрос завершился ошибкой | 400 при невалидном теле, 404, если проект или версия не существуют либо недоступны ключу, 422, если Git недоступен, отклонил учётные данные или источник невалиден, 429 при превышении квоты |
Конверт ошибки содержит машинный code и details[]; при 429 нужно подождать до момента, указанного в заголовке X-RateLimit-Reset (время Unix в секундах) |
| Повтор упавшего задания | Импорт с тем же Idempotency-Key возвращает исходный ответ и не создаёт вторую версию |
Использовать стабильный ключ, например производный от идентификатора запуска конвейера |
Коды ошибок, специфичные для переноса версий: DF_API.GIT_CONNECTION_FAILED (422), если репозиторий недоступен или учётные данные отклонены, DF_API.GIT_PUSH_FAILED (422), если отправка отклонена из-за того, что ветка изменилась во время экспорта или защищена, и DF_API.UNPROCESSABLE_ENTITY (422), если источник импорта невалиден или превышает ограничения по объёму.
Замечания и ограничения
- Операции, выполненные через публичный API, не попадают в журнал экспорта и импорта версий, который виден в интерфейсе; каждая из них фиксируется записью
API_ACCESSв журнале аудита, а для операций записи — со снимками до и после - Multipart-эндпоинты —
import/file, а также файловая веткаimport/validateиimport/preview— работают только в установке on-premises - Подключение, созданное через
save_connection, является личным: его видят только создатель и администраторы, а решение о предоставлении общего доступа принимает администратор