Ядро

Составные документы: `import`

И -f test.yaml, и -c config.yaml принимают верхнеуровневый ключ import:, указывающий базовый документ. База загружается первой — рекурсивно, так как она сама может импортировать свою базу, — а затем импортирующий документ глубоко объединяется поверх неё. Команды используют общий согласованный базовый документ (профиль нагрузки, переменные, пороги), а каждый сервис переопределяет только то, что отличается.

# perf/config/_base.yaml — owned by the platform team
vus: 50
duration: 5m
variables:
  region: eu-west
  base_url: https://staging.example.com

# services/checkout/perf.yaml — owned by the checkout team
import: ../../perf/config/_base.yaml
variables:
  base_url: https://checkout.staging.example.com   # region inherited

Семантика слияния

  • Объекты сливаются по ключам, рекурсивно — variables: из базы и из импортирующего файла объединяются; при совпадении имён ключей побеждает импортирующая сторона.
  • Всё остальное замещается — скаляры и массивы, включая steps:. Тестовый документ, определяющий steps:, целиком замещает список базы (пошагового сращивания нет); документ без steps: наследует список базы без изменений.
  • Цепочки работают: A импортирует B, B импортирует C → C загружается первым, B сливается поверх него, A — поверх результата. Циклы завершаются ошибкой с выводом всей цепочки.

Валидация выполняется над объединённым документом — и perfscale lint, и perfscale run сначала разрешают импорты, поэтому ошибка указывает на то, что реально будет выполняться.

Формы источников

# 1. relative filesystem path — resolved against the importing file's dir
import: ../shared/_base.yaml

# 2. raw HTTP(S) URL — the ref is already pinned in the path
import: "https://raw.githubusercontent.com/org/repo/v1.2.0/perf/config/_base.yaml"

# 3. a file at a ref of any git remote (SSH or HTTPS, self-hosted included)
import:
  git: git@gitlab.example.com:group/repo.git
  ref: v1.2.0          # tag, branch, or commit SHA
  file: perf/config/_base.yaml

Git-импорты вызывают системный git (clone --depth 1), поэтому применяются SSH-ключи, credential helpers и настройки прокси — приватные репозитории работают с той аутентификацией, которая уже настроена для git clone. Зависимости от libgit2 нет.

Безопасность: удалённые импорты включаются явно

Разрешение импортов происходит при загрузке конфигурации — до того, как сработают ограничения allow_file_actions / allow_process_actions. Если сама конфигурация получена из недоверенного источника (скажем, файл из PR), сетевой import: внутри неё — это примитив SSRF и вектор атаки на цепочку поставок: удалённая база может содержать allow_process_actions: true вместе с шагом std/child_process@v1.

Поэтому разрешение на обращение к сети даёт вызывающая сторона, а не документ:

perfscale run -f t.yaml -c c.yaml --allow-remote-import
perfscale lint c.yaml --allow-remote-import

Без флага любой импорт по URL или из git завершается ошибкой с пояснением. YAML-поля, включающего такой доступ, намеренно не существует — документ не может сам выдать себе доступ к сети.

Источники остаются изолированными:

  • Документ, загруженный по URL, разрешает относительные импорты относительно собственного URL. Указать локальный путь файловой системы он не может.
  • Документ из git-репозитория может импортировать только файлы внутри того же клона; выходы за пределы корня репозитория через ../ отклоняются.
  • Только локальные документы на вашем диске могут начинать цепочку в сеть (и только при наличии флага).

Кэширование

Git-клоны размещаются в ~/.cache/perfscale/imports/<hash(git+ref)> (учитывается $XDG_CACHE_HOME; $PERFSCALE_CACHE_DIR переопределяет корень).

Тип refПоведение
Commit SHAНеизменяемый — кэшируется навсегда, повторно не запрашивается
TagСчитается неизменяемым — кэшируется навсегда; --refresh-imports запрашивает заново
BranchИзменяемый — после 5-минутного TTL удалённый репозиторий перепроверяется через git ls-remote; сдвинувшаяся ветка запрашивается заново, недоступный удалённый репозиторий откатывается к кэшированной копии с предупреждением [sys]

Правило для веток важно: без него плавающий ref: main молча застыл бы на том, что увидела первая загрузка, и каждый последующий запуск утверждал бы, что тестирует «последнюю версию», используя устаревшую базу. --refresh-imports принудительно выполняет проверку немедленно.

HTTP-импорты запрашиваются при каждом запуске (для воспроизводимости закрепляйте в URL тег или SHA).

Взаимодействие с остальным движком

  • import собирает документ; это не шаг, и время итераций он не расходует. После слияния результат валидируется по той же JSON Schema, что и любой обычный файл.
  • Интерполяция ${{ vars.* }} происходит во время выполнения, после слияния, — база может ссылаться на переменные, задаваемые импортирующим файлом, и наоборот.
  • perfscale lint сообщает о проблемах разрешения импортов (отсутствующая база, цикл, заблокированный удалённый источник) как об обычных находках; после разрешения импортов он валидирует объединённый документ, включая проверку импортированных шагов по схеме GraphQL.

Ограничения

  • Один import на документ (вместо перечисления нескольких — выстраивайте базы в цепочку).
  • Никакой пошаговой хирургии слияния: steps: замещается целиком.
  • Цепочки импортов ограничены 16 уровнями; тела HTTP-ответов — 10 МБ.

Готовый к запуску пример пары лежит в examples/import/.