Перейти к содержимому
Elemctl
Русский
Esc
↑↓navigate↵open⌘Jpreview
На этой странице

Платформа изнутри

Console API v2, формат файла сборки и особенности поведения платформы, о которых стоит знать.

На этой странице – про платформу, а не про инструмент: как устроен Console API, из чего состоит файл сборки и как платформа ведёт себя в случаях, которые с первого раза удивляют. Всё это записано потому, что документация платформы такие углы не покрывает, а elemctl пришлось их изучить на практике.

Для работы с инструментом эта страница не нужна. Она пригодится, когда что-то ведёт себя странно и хочется понять, что происходит внутри, – или когда вы пишете собственный клиент.

Контракт Console API v2

Общий префикс: {base}/console/api/v2. Тела запросов и ответов – JSON, кроме загрузки сборки. Имена полей пишутся в kebab-case.

Новый справочник консоли называет основной версией Console API 2.1 с префиксом {base}/console/api/v2.1, а версия 2.0 лежит рядом и работает. elemctl остается на 2.0 и обращается к 2.1 только за тем, чего в 2.0 нет: за расширениями, примененными в приложении. На путь, для которого у консоли нет обработчика, она отвечает 401, а не 404, с текстом Handler of HTTP request "[<метод>] <путь>" in application "console" not found. – так сервер старше метода от него и отказывается. Новый токен получает тот же ответ, поэтому elemctl читает текст ответа до обновления токена и называет ответ методом, которого сервер не знает, а не неудачным входом.

Приложения

  • GET /applications - список. Query-параметр name у метода есть, но платформа его игнорирует и возвращает полный список. Это проверено живым вызовом, поэтому по имени фильтрует клиент.
  • GET /applications/{id} - карточка. Значимые поля ответа: id, status, uri (адрес работающего приложения), error (текст ошибки, если есть), technology-version, date-updated, display-name, publication-context, source (объект с информацией об источнике, содержит в т.ч. project-version - версию применённой сборки).
  • POST /applications - создать. Тело:
    • source - объект {"type": "repository"} плюс ровно один из ключей: project-version-id (id сборки-источника) либо image-id (id проекта);
    • display-name, publication-context - имя и путь публикации;
    • development-mode - булево, создавать ли среду разработки;
    • необязательные space-id, technology-version.
  • DELETE /applications/{id} - удалить.
  • PUT /applications/{id}/status/start - запустить.
  • PUT /applications/{id}/status/stop - остановить.
  • POST /applications/{id}/actions/debug - данные для сессии отладки (ApplicationDebugInfo: {"debug-token": ..., "debug-address": ...}). Тело запроса пустое; требует включённой отладки на сервере (config/debug.yml enabled: true).
  • POST /applications/{id}/project/update - применить сборку к приложению. Тело: {"source": {"type": "repository", "image-id": "<id сборки>"}} либо {"source": {"type": "repository", "project-id": "<id>", "assembly-version": "<версия>"}} (assembly-version необязательна). Сборку расширения принимает тот же метод и применяет рядом со сборкой приложения: source.project-version-id карточки по-прежнему называет сборку приложения, а расширение появляется в extension-projects метода GET /v2.1/applications/{id}/project, с версией сборки в assembly-version. elemctl проверяет такое применение по этому перечню.
  • POST /applications/{id}/project/export – сборка, на которой работает приложение; справочник описывает метод и в 2.0, и в 2.1. Тело запроса пустое, ответ – сам архив, application/octet-stream без Content-Disposition, с файлами сборки, упакованными заново. Консоль берет его у сервера приложения, и расширений, примененных в приложении, в нем нет. Во время обновления и после неудачного обновления ответом приходит сборка, загруженная для этого обновления. Сборка, загруженная в проект, возвращается с номером, который дал ей сервер, в манифесте. Обработчик 2.1 принимает и недокументированный параметр запроса extension-id и тогда отвечает сборкой этого расширения. elemctl apps export записывает архив под именем {Имя} {Версия}.xasm.
  • POST /applications/{id}/dumps - создать дамп. Тело: include-users, include-binary-data (булевы), description (строка).
  • GET /applications/{id}/dumps/{dumpId} - статус дампа.
  • GET /applications/{id}/users – пользователи, подключённые к приложению, каждый {user-list-id, user-id, presentation, is-admin, token-access-enabled}. Среди них и пользователи панели управления: их платформа подключает сама и называет такого пользователя по логину. token-access-enabled – доступ пользователя к HTTP-сервисам приложения по токену; без него вызов сервиса токеном этого пользователя получает 500 “Token access is denied”.
  • PUT /applications/{id}/users/change-token-access – переключить этот доступ. Тело: {"user-list-id", "user-id", "enable-access"}, все три обязательны; ответ – подключение в том виде, в каком оно стало. Пользователь, не подключённый к приложению, получает 500 “Can’t change user token access” без объяснения причины. Метод описан и в v2, и в v2.1. elemctl apps token-access сначала находит подключение, а после переключения перечитывает признак; весь перечень печатает elemctl apps users.
  • GET /me – пользователь, которому принадлежат реквизиты: id, login, presentation, user-list-id.
  • GET /v2.1/applications/{id}/project – проекты, которые работают в приложении: application-project и примененные расширения в extension-projects, у каждого id, project-id, assembly-id, enabled, order, vendor-name, project-name, project-presentation, project-version и assembly-version. id – ид расширения на сервере приложения: Ид из Проект.yaml расширения, тот configuration-id, которым ответила его загрузка. В assembly-id расширения консоль кладет ид его проекта, так что сборку это поле не называет. Тот же путь в 2.0 отвечает одним проектом приложения.
  • POST /v2.1/applications/{id}/project/{ExtensionId}/export – сборка примененного расширения. Тела нет, ответ – сам архив, application/octet-stream без Content-Disposition, в нем файлы, с которыми сборку загрузили. Сегмент пути – id расширения, регистр букв не важен. Справочник называет параметр идентификатором проекта расширения, но на project-id сервер отвечает тем же голым 500 “Unable to export extension … from application …”, что и на неизвестное значение. Остановленное приложение отдает сборку так же. В 2.0 такого метода нет. elemctl apps export-extension сначала ищет расширение по любому из его имен и сохраняет архив под именем {Имя} {Версия}.xasm.

Статусы приложения: стабильные Running, Stopped, Error; переходные Starting, Stopping, Initializing, Updating, Frozen, Creating, Deleting. Во время переходов поле status может быть и пустым. Справочник описывает status простой строкой и значений не перечисляет, поэтому перечень собран по живым проверкам. Deleting держится, пока идёт задача DeleteApplication, а потом приложение остаётся в перечне со статусом Deleted. UNKNOWN заглавными встречен у приложения, у которого пропали файлы базы: его нельзя было ни запустить, ни удалить – удаление начинается с выгрузки данных. Этот статус консоль дает состоянию приложения на сервере, которому у нее нет имени, поэтому ничто не говорит, что он окончательный, и клиент не считает его ни стабильным, ни переходным: ожидание статуса терпит UNKNOWN минуту подряд, потом останавливается и называет статус, а не ждет до конца всего срока.

Версия технологии

  • Чтение - из поля technology-version карточки приложения (отдельный endpoint чтения есть не во всех версиях платформы - не использовать).
  • Обновление: POST /tasks/group-tasks/update-applications-technology, тело {"technology-version": "<версия>", "applications": ["<app-id>"]}. Возвращает групповую задачу; её статус - GET /tasks/group-tasks/{taskId}.

Пространства и проекты

  • GET /spaces - список пространств.
  • GET /projects - список проектов; GET /projects/{id} - карточка; DELETE /projects/{id} - удаление. Вид проекта лежит в project-kind: Application, Library или Extension, а Group - у группы проектов, которую перечень несет рядом с ее участниками.

Сборки проекта (assemblies)

  • Загрузка файла сборки - бинарный POST (Content-Type application/octet-stream, тело - байты файла):
    • POST /projects/{id}/assemblies - добавить сборку в существующий проект;
    • POST /projects - создать новый проект из сборки либо добавить сборку в проект с тем Ид, что записан в ее Проект.yaml;
    • POST /spaces/{space-id}/projects - то же, но новый проект заводится в этом пространстве. Пространство загрузки в проект уходит необязательным query-параметром space-id, так, как его пишет справочник. Живая проверка показала, что сервер его читает: ид, который не UUID, отклоняет ответом 400, а неизвестное пространство – ответом 404. SpaceId в PascalCase, который клиент отправлял раньше, проходил непрочитанным. У POST /projects параметра пространства в справочнике нет, и сервер не читает там ни одного написания: новый проект, загруженный с space-id=not-a-uuid, всё равно создался. Поэтому пространство нового проекта уходит в путь POST /spaces/{space-id}/projects. Неизвестное пространство этот метод отклоняет ответом 404, а сборку, у Ид которой проект уже есть, добавляет в этот проект, как и POST /projects. Коммит, из которого собрана сборка, уходит параметром commit-id: справочник описывает его у POST /projects/{id}/assemblies, и сервер кладёт его в карточку сборки. У того же метода справочник перечисляет ещё branch-name, commit-message и modified, и живая проверка показала, что сервер их принимает, а в карточке не показывает ни одного: его branch-name – это ветка групповой разработки, а не git, а modified=1 он отклоняет ответом
    1. CommitId, BranchName и CommitMessage в PascalCase – вовсе не параметры метода, их сервер игнорирует. У POST /projects параметра коммита в справочнике нет, поэтому сборка, которая создаёт свой проект, коммита не несёт. elemctl отправляет один коммит, а ветку, признак незакоммиченных правок и каталог исходников держит в локальном реестре загрузок. Версию архива две загрузки понимают по-разному. Загрузка в проект, POST /projects/{id}/assemblies, её вовсе не читает: базу сервер берёт из Версия описания проекта, а номер дает наибольший, какой он когда-либо выдавал в этой базе, плюс один. Проверено живьём: архив 1.0.0-i1 стал 1.0.0-1, следующий, 1.0.0-10001, – 1.0.0-2, 1.0.0-2, который в проекте уже был, – 1.0.0-3, а 1.0.1-7 – 1.0.0-502 в проекте, чье описание говорит 1.0.0. Номер удаленной сборки остается за ней: перечень ее больше не показывает, а сервер все равно считает от него. Когда с верха перечня сняли 1.0.0-4, следующая загрузка стала 1.0.0-5, а после снятия 1.0.0-50 и 1.0.0-51 – 1.0.0-52, хотя перечень кончался на 1.0.0-8; сборка, снятая из середины перечня, ничего не меняет. Прочитать счетчик негде: в карточке проекта его нет, перечень удаленную сборку не показывает, а карточка удаленной сборки отвечает 404. Эта загрузка отвечает карточкой сборки, и её assembly-version – номер, который выдал сервер. Загрузка без ид проекта, POST /projects, версию архива сохраняет как есть, в том числе с нечисловым суффиксом, а на версию, которая в проекте уже есть, отвечает 409 (ниже). Номер удаленной сборки такая загрузка может занять снова, и счет проекта от этого назад не идет: архив 1.0.0-2 лег под этим номером после того, как прежнюю 1.0.0-2 сняли, а следующая загрузка в проект получила 1.0.0-8, а не 1.0.0-3. Поэтому суффикс версии, например номер запуска CI, переживает второй путь и никогда первый. elemctl deploy называет версию, которую сервер не сохранит, до сборки, а сборку, которую он пронумеровал иначе, – сразу после загрузки. Без явной версии деплой считает номер от большего из перечня сборок и локального реестра загрузок, который помнит номера, полученные загрузками этой машины, поэтому сборка, которую эта машина загрузила и сняла, счет назад не отбрасывает. Сборки, загруженной с другой машины и потом снятой, нет ни там, ни там, и тогда строка после загрузки говорит, что номер назначил сервер. Ответ несёт id созданной сборки в одном из полей: image-id, assembly-id или id. Проверять их надо в этом порядке. Там же лежит объект artifact, который называет проект, куда сборка легла: artifact-id - ид проекта, он открывается карточкой проекта; configuration-id - Ид из Проект.yaml; name - представление проекта. Панель показывает проект под представлением последней залитой сборки, то есть под Представление из ее Проект.yaml, а не под Name из манифеста: сборки с одним Ид, у каждой из которых были новые Name и представление, всякий раз оставляли в карточке проекта представление. Поэтому сборка, залитая в проект под другим именем, молча этот проект переименовывает, а сборка с родным именем возвращает имя обратно. Про такое несовпадение elemctl предупреждает перед загрузкой. Загрузка без ид проекта переименовывает проект так же, когда ее Ид ведет в проект с другим именем. До такой загрузки сравнивать нечего: проект выбирает сервер, а метода, который находит проект по Ид, нет. Поэтому builds upload читает перечень проектов до загрузки и сверяет с ним artifact ответа: называет проект, куда легла сборка, говорит, создала ли его загрузка, и предупреждает, если она его переименовала.
  • Проект опознается по Ид из Проект.yaml, это configuration-id ответа. Поэтому POST /projects создает проект не всегда: если живой проект с таким Ид есть, сначала в пространстве загрузки, потом в любом другом, сборка просто добавляется в него, какие бы поставщик и имя ни стояли в манифесте, и он же приходит в artifact-id. Пара Vendor + Name – не ключ, а ограничение: у пары в пространстве один проект. Живой опыт развел их: сборки acme/crm, acme/crm-next и globex/crm с одним Ид легли в один проект, и он всякий раз брал имя очередной сборки, а сборка acme/crm с новым Ид получила 409. Ответ 409 ALREADY_EXISTS приходит в двух случаях. Первый: залита версия, которая уже есть, текст ответа - “Версия сборки … уже присутствует в группе проекта”. Второй: новый Ид с поставщиком и именем, которые уже держит другой проект пространства, текст - “Сборка с именем поставщика … уже зарегистрирована в другом проекте”; проект, который консоль завела под этот Ид, уходит вместе с отказом. Отдельный одноразовый проект под те же исходники получается только с новым Ид и новой парой вместе.
  • GET /projects/{id}/assemblies - список сборок. Элемент содержит assembly-version, строку вида 1.0-42, и id в поле id либо image-id. Ответ приходит массивом или объектом, у которого список лежит в поле items или assemblies. Страниц у метода нет, и общего числа он не сообщает. Параметры limit, size, pageSize, count, top, maxResults, page, pageNumber, offset, skip, from и start метод игнорирует: ответ на все эти запросы совпадает побайтно, а ни в заголовках, ни в теле нет ни счётчика, ни курсора. Проверено живыми вызовами на двух установках. Платформа сама удаляет сборки, которыми никто не пользуется, и возраст тут ни при чем: справка вендора называет это автоматическим удалением неиспользуемых сборок. Делает это консоль в один момент – когда приложение заканчивает применение сборки: фоновое задание перебирает сборки проекта этого приложения с той же базовой версией, что у сборки, на которой оно теперь работает. Остаются релизная сборка, защищенная сборка, сборка проекта по умолчанию, сборка, на которой работает живое приложение проекта или которая в него загружена, и сборка с наибольшим номером этой базы. Остальное снимается, не больше десяти сборок за одно применение. Сборка, загруженная без ид проекта (POST /projects), защищена с момента загрузки, как и та, из которой создан репозиторий проекта, и никакое применение ее не снимет, а сборка, загруженная в проект по ид (POST /projects/{id}/assemblies), не защищена. Библиотечная сборка, от которой не зависит ни одна оставшаяся сборка, уходит вместе со сборками своей группы. Сборки проекта-расширения вне уборки вовсе. Расширение применяется тем же обновлением приложения, и задание после него начинает со сборки, которую называет карточка приложения: перебирает проект самого приложения и снимает неиспользуемые сборки этой базы там, а проект расширения хранит все сборки, в том числе те, на которых не работает ни одно приложение, пока их не удалят вручную или вместе с проектом. Живая проверка сошлась с исходником: приложение работало на 1.0.0-1 своего проекта, в тот же проект по ид были загружены 1.0.0-2 и 1.0.0-3, а в проекте расширения лежали сборки с 1.0.0-1 по 1.0.0-4. Применение сборки расширения 1.0.0-2 сняло 1.0.0-2 проекта приложения и ничего в проекте расширения, а применение 1.0.0-4 следом оставило сборки расширения 1.0.0-2 и 1.0.0-3 на месте. Поэтому проект, к приложениям которого ничего не применяют, хранит все сборки, а сборка, загруженная впрок, переживает применение другой, только пока у нее наибольший номер. Так написано в исходниках консоли из дистрибутива сервера, и живая проверка на проекте из девяти сборок сошлась с ними в каждой строке. Создание приложения за две минуты не сняло ничего. Применение 1.0.0-3 еще до ответа сняло 1.0.0-2, 1.0.0-4 и 1.0.0-11, а оставило 1.0.0-1 и 1.0.0-10, обе загружены без ид проекта, 1.0.0-3, 1.0.0-12 с наибольшим номером и обе сборки 2.0.0. Применение 1.0.0-12 сняло 1.0.0-3. Применение 2.0.0-1 не сняло ничего, в том числе 1.0.0-12: это применение смотрело только на 2.0.0. Раньше видели и такое: из семнадцати сборок одной дневной серии в перечне осталась одна, та, на которой работает приложение, а сборка двухмесячной давности жива: она у проекта первая, а загрузка, которая создает проект, идет без ид проекта. Отсюда требование к клиенту: называть перечень остатком, а не историей проекта и не ее страницей.
  • GET /projects/{id}/assemblies/{version} - карточка сборки, DELETE .../{version} - удаление. Последний сегмент - это версия, как метод её и называет: assembly-version или project-version, строка вида 1.0-42. Ид карточки сюда не подходит: на UUID метод отвечает 404 “Assembly with version not found”. Проверено живыми вызовами на двух установках разного возраста, ведут себя одинаково. При этом ид - тоже адрес, который есть на руках: его печатает перечень сборок и возвращает загрузка. Поэтому elemctl принимает обе формы. Он находит по списку сборок ту версию, которую метод ждёт, а если адрес отклонён с 400 или 404, пробует сегментом и ид - на случай установки, которой нужна эта форма. Версия сборки, загруженной в проект, – номер, который дал сервер, а не версия её архива. Удаление сборки платформа отклоняет с 500, пока живо созданное из неё приложение. Когда это приложение действительно исчезло, тот же запрос проходит. Сборка, которая лишь участвовала в применении, пусть даже откатившемся, удаляется без препятствий.

Версии сборок сравнивают по числовому суффиксу после последнего дефиса: 1.0-10 новее, чем 1.0-9. Лексикографическое сравнение даёт неверный порядок.

Ветки среды разработки

  • GET /branches - список; необязательные query project-id, name.
  • GET /branches/{id} - карточка. Поля: name, kind, project, application, source-branch, deletion-mark, version-stamp.
  • POST /branches - создать. Тело: name, kind: "development", project: {"id": "<id>"}, необязательно application: {"id": "<id>"}.
  • PUT /branches/{id} - изменить. Платформа держит оптимистическую блокировку, поэтому сначала прочитайте карточку, а затем отправьте тело, собранное из текущих значений. Это name, kind, deletion-mark и version-stamp, который обязательно вернуть как есть. Поля source-branch и application свернуть до {"id": ...}, а если ид нет, то до {"name": ...}. Чтобы перепривязать ветку к приложению, замените application на {"id": "<новый app-id>"}.
  • Изменения ветки принимает тот же PUT /branches/{id} с дополнительным ключом тела write-parameters: {"merge": true}.
  • DELETE /branches/{id} - удалить ветку.

Инструмент работает только с документированным Console API v2. Внутренние, то есть недокументированные, API консоли платформы он не использует и не описывает.

Списки пользователей

Список пользователей держит либо пользователей приложения, либо пользователей панели управления. У приложения список свой и назван по нему, а карточка приложения указывает на него полем default-user-list. У панели управления список один на установку.

  • GET /user-lists - список: id, presentation, space-id. Фильтра по имени на сервере нет. GET /user-lists/{id} - полная карточка. POST /user-lists создаёт список и требует карточку целиком: на неполное тело он отвечает 500 с обманчивым текстом “Failed to parse json”. DELETE /user-lists/{id} удаляет.
  • GET|PUT /user-lists/{id}/settings/self-registration - {enabled, phone-required, email-required}, это “Разрешить пользователям регистрироваться самостоятельно” из панели.
  • GET|POST /user-lists/{id}/settings/account-services-settings, PUT|DELETE .../{account-service-id} - сервисы учётных записей. Запись: {account-service-id, account-service-type, local-id, enabled, create-user-on-auth, additional-settings}; тип Local аутентифицирует паролем, остальные (OIDC, Cas, ActiveDirectory, Esia) внешние. Обе записи требуют запись целиком.
  • GET|POST|DELETE /applications/{id}/userlists - ид списков, подключённых к приложению. Обратите внимание на написание: здесь userlists, а на верхнем уровне user-lists. Собственных настроек у связи нет.
  • GET /user-lists/{id}/users – пользователи списка, среди полей id и login. Запись несёт и токены доступа пользователя вместе с секретами, так что в журнал такой ответ как есть не попадает.

Что стоит знать, прежде чем на это опираться:

  • правила разбора ответа сервиса учётных записей принимаются под ключом userPropertiesCalculationRules. Это presentation-rule, email-rule, phone-rule и response-kind. Схема самого справочника называет поле calculation-rules, но на такое написание приходит 400. GET правила не возвращает никогда: настройка только на запись, и клиент API не может подтвердить, что она применилась;
  • состава форм аутентификации приложения в API нет вовсе, как и настройки подключения “пользователи списка подключаются автоматически при входе”. Это остаётся в панели;
  • GET сервиса учётных записей отдаёт client_secret OIDC-клиента открытым текстом, так что таким ответам не место в логах и отчётах как есть;
  • неизвестный путь Console API отвечает 401 с текстом “Handler of HTTP request … not found”, а не 404. При переборе поверхности именно это и говорит, что метода нет.

Задачи приложений

GET /tasks/application-tasks - список задач всех приложений. Серверного фильтра нет, фильтровать приходится на клиенте. Поля задачи: id, application-id, status (в т.ч. Error, Failed), operation-type, error-message, start-date (ISO 8601, может оканчиваться на Z).

Формат файла сборки (.xasm / .xlib)

Файл сборки - ZIP-архив (deflate):

  • в корне Assembly.yaml - манифест, плоские пары ключ-значение:

    ManifestVersion: 1.0 | 1.1
    ProjectKind: Application | Library | Extension
    Vendor: <поставщик>
    Name: <имя проекта>
    Version: <версия, например 1.0-42>
    Created: <UTC, формат YYYY.MM.DD HH:MM:SS>
    BranchName: <имя git-ветки>
    CommitId: <хэш коммита>

    Для библиотеки (ProjectKind: Library) в конце добавляется строка Release: (пустое значение); расширение файла .xlib, для приложения и расширения – .xasm.

    Расширение (ProjectKind: Extension) пишется с ManifestVersion: 1.1, остальные виды – с 1.0. Читателя манифеста сервер выбирает по его версии, а читатель 1.0 знает только Application и Library: расширение с манифестом 1.0 применение отвергает с “Неизвестный вид проекта: "Extension"”. Ту же пару консоль пишет расширению.

  • далее файлы проекта путями {vendor}/{name}/... - относительно корня репозитория. Каталог проекта обязан лежать по схеме {repo}/{vendor}/{name}/Проект.yaml. Разделители путей в архиве - прямые слэши (и на Windows).

Имя файла сборки: {Имя} {Version}.xasm (через пробел).

Метаданные проекта берутся из Проект.yaml. Это YAML, и достаточно разобрать плоские пары “ключ: значение” верхнего уровня, а вложенные строки с отступом пропустить. Двуязычие исходников платформа заявляет сама, дескриптор с английскими ключами применяется штатно, поэтому каждый ключ читается в обоих написаниях: Имя/Name, Поставщик/Vendor, Версия/Version (базовая, например 1.0) и ВидПроекта/ProjectKind. Значение Библиотека или Library означает библиотеку, Расширение или Extension – расширение, все прочее – приложение. Если встретились сразу оба написания ключа, побеждает русское. Двуязычны и имена служебных файлов: конвертер платформы принимает Project.yaml и Проект.yaml, Subsystem.yaml и Подсистема.yaml. У английского дескриптора английские и значения перечислений, например VisibilityScope: Global.

Если версия сборки не задана явно, она собирается как {базовая версия}-{N+1}, где N - счетчик из версии последней сборки проекта той же базовой версии. deploy берет за N больший из двух номеров: номер последней сборки в перечне и наибольший номер, который локальный реестр загрузок помнит для проекта в этой базе. Сервер считает от наибольшего номера, какой он когда-либо выдавал, считая и удаленные сборки. Проект, поднятый на новую базовую версию, снова начинает с -1, какие бы номера ни набрала старая база. Когда последней сборки этой базы нет, суффикс берётся из номера запуска CI в окружении: первое числовое значение из CI_PIPELINE_IID, GITHUB_RUN_NUMBER, BUILD_NUMBER, именно в этом порядке. Иначе чистый рабочий каталог CI давал бы -1 каждый раз. Нет и номера CI - остаётся {базовая версия}-1.

Git-метаданные, то есть хэш коммита и имя ветки, берутся из git-репозитория, который содержит каталог проекта. Если git недоступен, поля остаются пустыми.

Отбор файлов в архив:

  • включаются только расширения: .yaml .xbsl .xbql .md .txt .json (исходники), .png .svg .jpg .jpeg .gif .webp .ico (изображения), .css .htm .html .js .woff .woff2 .ttf .eot (веб-ресурсы);
  • внутри каталога Ресурсы отбор по расширению не действует, на любом уровне и в подкаталогах тоже: по документации платформы ресурс - это произвольный файл, поэтому оттуда берётся всё;
  • файлы описания клиента SOAP-сервиса включаются, где бы они ни лежали: <ИмяКлиента>.Wsdl.<n> и <ИмяКлиента>.Xsd. Платформа держит их рядом с элементом проекта и читает по имени;
  • исключаются каталоги .git, .claude, .github, __pycache__, node_modules, .venv и все скрытые (начинающиеся с точки);
  • исключаются файлы .gitignore, .env, .DS_Store и файлы *.xasm, *.xlib.

Разбор готового архива

Операция, обратная сборке: по файлу .xasm или .xlib читаются манифест, свойства проекта из его Проект.yaml внутри архива и состав. Она нужна, чтобы подключить библиотеку к проекту, не разворачивая её исходники.

Раскладка внутри проекта; о составе достоверно говорят только каталоги:

  • каталог первого уровня - это подсистема. Файл Подсистема.yaml необязателен, у подсистемы библиотеки его может не быть вовсе, поэтому при поиске подсистем на него опираться нельзя;
  • вложенный каталог подсистемы - это пакет. Файла-описания у пакета нет, и каждый каталог даёт сегмент имени;
  • полное имя типа: {vendor}::{name}::{подсистема}[::{пакет}]::{ИмяТипа}. То же имя без последнего сегмента указывается в Использование и импорт.

Наружу, в подключивший проект, видны только типы с ОбластьВидимости: Глобально. По умолчанию стоит ВПодсистеме, а глобальная область пишется явно.

Совместимость проверяется по свойству РежимСовместимости файла Проект.yaml. Свойства ВерсияТехнологии в Проект.yaml не существует: оно относится к телу запроса Console API на создание приложения, а не к файлу проекта.

Поведение платформы, которое приходится учитывать

  1. Тихий откат применения. Когда применение сборки к приложению падает, например из-за ошибки компиляции, платформа молча откатывает приложение на предыдущую сборку и запускает его. Статус Running успеха не означает. Достоверно результат проверяют так:
    • взять задачи приложения (п. 4.6) со статусом Error или Failed, у которых start-date не раньше начала работы. Старые ошибки из истории не в счёт;
    • сверить фактически применённую версию, поле source.project-version карточки приложения, с версией загруженной сборки;
    • сборку расширения сверить вместо этого с расширениями приложения, extension-projects метода GET /v2.1/applications/{id}/project: карточка называет только сборку приложения. Расширение должно работать на версии сборки и быть включено (enabled). Сервер без Console API 2.1 этого не скажет, поэтому там применение расширения остается непроверенным, а не объявляется откатом;
    • для сведения сделать контрольный GET по uri приложения. Коды 401 и 403 нормальны для закрытых приложений и успеху не противоречат.
  2. Пустой каркас при создании. На части конфигураций платформы приложение, созданное с источником “проект”, то есть с image-id, равным ид проекта, выходит пустым, без данных проекта. Надёжный источник - конкретная сборка в project-version-id, например последняя сборка проекта.
  3. Удаление с черновиками. Если в среде разработки приложения есть неопубликованные правки, DELETE /applications/{id} возвращает 400 с FAILED_PRECONDITION в теле. Принудительного удаления в API нет, остаётся панель управления. Инструмент обязан дать понятную подсказку.
  4. Готовность нового приложения. После создания приложение какое-то время держится в переходных статусах и без uri, поэтому нужно предусмотреть ожидание готовности: появился uri и статус стал стабильным. Статус Error во время ожидания - сразу ошибка.
  5. Перезапуск после применения. project/update может сам перезапустить приложение. После вызова дождитесь выхода из переходных статусов. Если итог не Running, остановите приложение, когда оно не в Stopped, дождитесь Stopped, запустите и дождитесь Running. Разумные сроки ожидания: остановка около 3 минут, запуск и стабилизация около 5 минут, опрос примерно раз в 10 секунд.
  6. Статус Error конечный. Стабильный Error, например после неудачного применения, - это сразу ошибка: покажите тексты ошибок задач приложения. Останавливать и перезапускать такое приложение не надо, ждать другого статуса тоже: из Error оно в Stopped не переходит, и ожидание просто выберет весь отведённый срок.
  7. Windows. Временные файлы и кеши - только через tempfile. Вывод консоли надо перевести в UTF-8 вызовом reconfigure для stdout и stderr, иначе кириллица ломается.
  8. Проект опознается по Ид. Смотрите загрузку сборки выше: проект опознается по Ид из Проект.yaml, а пара Vendor + Name из манифеста лишь должна быть свободна от других проектов пространства. Загрузка без ид проекта означает не “создать новый проект”, а “положить туда, где этому Ид место”.
  9. Удаление идёт в фоне и по порядку. DELETE /applications/{id} возвращается сразу, а приложение ещё какое-то время живёт с задачей DeleteApplication. Пока оно существует, удаление сборки, из которой оно создано, отклоняется с 500. Порядок уборки такой: удалить приложение, дождаться, когда его карточка ответит 404 или статус станет Deleted, и только затем удалять сборку.
  10. Компиляция серверная и происходит при применении. Локальная сборка только упаковывает архив. Синтаксис, типы и видимость исходников проверяет серверный компилятор, когда сборку применяют или создают из неё приложение. Отдельной конечной точки “скомпилировать” нет. Из этого и из пунктов 8 и 9 собран elemctl probe: исходники уходят в свой же проект сборкой с одноразовой версией, до компилятора добираются через одноразовое приложение, а потом обе сущности удаляются. Рабочее приложение при этом ничем не рискует.
  11. Вход в только что созданное приложение. Новое приложение получает собственный пустой список пользователей, и карточка указывает на него полем default-user-list. Вход по логину и паролю в нём выключен, сервиса учётных записей у него нет. Поэтому учётные записи, которыми входят в другие приложения, здесь не работают. Подключение чужого списка пользователей через POST /applications/{id}/userlists вместе с включением локального входа этого не меняет. Работает учётная запись панели управления: её пользователей платформа подключает к приложению сама, и они входят сразу. Это полезно знать, когда поднимаешь стенд под задачу: из карточки способ входа не виден, а перебирать учётки не стоит, потому что у пользователя есть счётчик неудачных попыток.
  12. Сервер ещё стартует. После запуска или обновления сервер поднимает консоль не сразу: после одного обновления она молчала тринадцать минут. Всё это время на любой запрос к консоли, включая запрос токена, сервер отвечает 404 с текстом Application "console" not found. Тот же код получает отсутствующее приложение или сборка, и отличает их только текст. elemctl распознаёт такой ответ в своём клиенте и говорит, что он значит: сервер стартует, повторите, когда /console начнёт отвечать 302. deploy пережидает старт сам, не дольше --server-start-timeout секунд (по умолчанию 900, 0 – не ждать), и продолжает, как только консоль ответит. Запрос, который консоль отвергла, она не обработала, поэтому повторять его безопасно.
  13. Снятие применяется без вопросов. Сборку, в исходниках которой больше нет табличной части, сервер применяет как есть: табличная часть уходит, а с ней и её строки. Ни отказа, ни предупреждения, задача заканчивается успешно, и приложение работает дальше; так было со справочником, у которого в табличной части была строка. Так же уходят реквизит табличной части и реквизит самого объекта, вместе со значениями, и справочник, чьего описания в исходниках больше нет, вместе со всей таблицей: так было с пустым справочником. Та же потеря – описание, сделанное заново в своём файле, под прежними путём и именем, но с новым Ид: справочник с двумя строками вышел из такого применения пустым, а задача закончилась успешно. Сужение длины и смена типа пересоздают данные объекта, поэтому elemctl deploy их без явного разрешения отклоняет, и так же отклоняет снятый целиком справочник, документ или регистр. Снятие реквизита или табличной части он называет до сборки и идёт дальше: это сознательная правка исходников, и строка говорит, что применение заберёт.

Последнее обновление 29 сентября 2026 г.

Эта страница была полезной?