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

Перенос версий через 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_version
  • import/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, является личным: его видят только создатель и администраторы, а решение о предоставлении общего доступа принимает администратор