Cluster · разработчикам

Составной модуль

Здесь разберём пример из двух модулей. «Мастерская» отправляет сообщение с кнопкой, работает с файлами и HTTPS, запускает расписание и вызывает второй модуль. «Обработка текста» считает SHA256 и получает отдельное событие. Оба пакета рассчитаны на отдельную тестовую среду.

Исходники:

Файлы описания содержат весь interface: команды, кнопки, действия страницы, функции, события и задачи. При изменении обработчиков нужно обновить и описание.

Зависимость

В «Мастерской» значение HELPER является учебным ID второго модуля. Это не готовая публикация в каталоге.

Если собираешь собственные пакеты:

  1. Опубликуй «Обработку текста» непубличной версией и сохрани ID из результата публикации.
  2. В исходниках «Мастерской» замени HELPER на полученный ID.
  3. В её manifest.json замени тот же ID в dependencies и permissions.modules.call.modules.
  4. Опубликуй «Мастерскую» и установи оба пакета в отдельный тестовый юзербот.
  5. Прими нужные права и запусти оба экземпляра.

Создание публикации не запускает её код и не выдаёт доступ к аккаунту. Установка и запуск выполняются отдельно. Полный порядок публикации находится в руководстве.

Если экземпляр зависимости один, app.modules.call выберет его сам. Если их несколько, выбери нужный в настройках зависимости либо передай slot. Имя в карточке не заменяет ID в программном вызове.

Права

«Мастерской» нужны:

Второму модулю нужны db.read и db.write для собственных данных. Функция last возвращает последнюю сохранённую строку. Доступ к файлам и сети ему не выдаётся. Для чистого вычисления SHA256 отдельное право не требуется.

Права вызывающего модуля не заменяют права принимающего. Если второй модуль обращается к базе во время вызова или события, должны пройти проверки всей цепочки. В этом примере запись события требует db.write у обоих.

Команда и кнопка

Команда !workshop в аккаунте либо /workshop у личного бота создаёт форму с кнопкой. У команды и обработчика нажатия стоит access="owner". Дополнительно users=[ctx.sender_id] ограничивает форму отправителем команды.

Форму отправляет личный бот. Вызов команды в аккаунте не превращает кнопку в сообщение от аккаунта. Без подключённого личного бота этот пример кнопки не работает.

У кнопки стоит once=True. Ядро принимает её один раз. Повтор с тем же callback_data, другой отправитель или копия в другом сообщении не запускают действие. Принятое нажатие расходуется до завершения обработчика: если проверка закончилась ошибкой, для новой попытки вызови команду ещё раз.

Обработчик вызывает общую проверку, затем редактирует сообщение через ctx.edit и завершает нажатие через ctx.answer. Проверку можно вызвать и с сайта действием execute, передав поле text.

Функция и событие

Сначала «Мастерская» вызывает функцию digest второго модуля и ждёт результат. Функция получает строку, кодирует её в UTF-8 и возвращает SHA256 и размер в байтах. Длина строки и число байтов могут отличаться.

Недоступный модуль возвращает RuntimeError("unavailable"). При нехватке прав приходит RuntimeError("access_denied"). Если ожидание результата закончилось по времени, SDK поднимает TimeoutError. Это отдельный тип исключения, его нужно обработать наряду с ожидаемыми ошибками ядра. Необработанная ошибка отключает экземпляр.

После успешных сетевых проверок вызывается emit с событием received. Он возвращает запись постановки в очередь. Это ещё не означает, что второй модуль обработал событие. В нашем примере результат события проверяется отдельно по его данным.

call ждёт ответа, emit только ставит событие в очередь. Если первый модуль обязан получить подтверждение обработки, используй функцию с результатом.

Файлы

Для строки создаётся временный report.txt. Модуль получает его обратно через app.files.chunks, сверяет байты и отправляет файл в HTTPS-запросе. Ответ второго запроса сохраняется как response.json, также читается и проверяется.

Оба готовых файла удаляются в finally. Так ошибка внешнего сервиса не оставляет ненужные файлы после каждого запуска. Для долговременного результата файл можно сохранить, но тогда он занимает квоту до удаления или истечения ttl.

При сбое app.files.upload SDK пытается удалить незавершённую загрузку. Если право уже отозвано или соединение с ядром потеряно, немедленная очистка может быть недоступна. Срок незавершённой записи составляет переданный ttl либо 3600 секунд от создания. После его истечения ядро удаляет запись при очередной очистке. Исходная ошибка загрузки при неудачной очистке сохраняется.

Действие filecheck проверяет пустой файл и файлы до 200000 байтов. Части передаются по 65536 байтов. Разрез внутри символа UTF-8 допустим для бинарного файла: декодирование должно выполняться после сборки всех частей, если формат вообще требует текста.

files.read относится к доступным файлам своего экземпляра. Чужой ID сам по себе не выдаёт доступ. Для файлов, явно переданных ядром через поле интерфейса, действуют отдельные разрешения чтения.

Сеть

Пример обращается к публичному учебному сервису postman-echo.com. Он получает переданные тестовые данные. Не подставляй в этот пример настоящие сообщения, токены и личные файлы.

Проверяются три операции:

  1. web.request: HTTPS GET с параметром запроса, проверка status и JSON.
  2. web.upload: передача байтов файла как тела POST. Это не multipart/form-data.
  3. web.download: сохранение ответа в собственный файл с пределом 8192 байта.

Код отдельно проверяет HTTP-статус. Ответ 404 или 500 сам по себе не вызывает исключение SDK. Ошибку сети, запрет адреса и ошибку содержимого ответа также нужно различать в рабочем модуле.

Действие fetch позволяет проверить ограничения адреса. С принятыми правами примера разрешён только указанный домен. HTTP и нестандартный порт отклоняются. Внутренние адреса запрещены даже при неограниченном web.request. Перенаправления автоматически не выполняются.

Размер обычного ответа и размер результата загрузки файла на сервер ограничены отдельно. Подробности находятся в описании вебклиента. Доступность учебного сервиса не гарантируется Cluster.

Расписание

Кнопка schedule создаёт задачу tick с первым запуском через три секунды и повтором через пять. ID сохраняется в данных экземпляра. Повторное действие получает ту же задачу, а не создаёт вторую.

Расписание остаётся после остановки и перезапуска модуля. init только восстанавливает страницу и не создаёт новую задачу. Сам обработчик tick увеличивает счётчик и обновляет страницу.

Повтор планируется после завершения предыдущей задачи. Пропущенные интервалы не превращаются в серию одновременных запусков. Расписание не обещает точности до секунды: учитываются работа ядра и состояние экземпляра.

Действие cancel отменяет и удаляет задачу, затем удаляет её ID из данных. Если задача выполняется прямо сейчас, отмена останавливает среду всего экземпляра. В таком случае исходное действие может не успеть вернуть результат или удалить запись. После запуска проверь состояние задачи и при необходимости повтори очистку. Для работающего продукта это стоит учитывать в интерфейсе.

Сохранённый ID должен соответствовать существующей задаче. Если пользователь удалит её через управление расписанием, модуль должен обработать устаревшую ссылку. Этот пример восстанавливает её при следующем действии schedule.

Проверка

Программа scripts/probe_workshop.py создаёт отдельное ядро, базу, ключ, тестовые профили и два контейнера gVisor. Она не читает рабочую базу и не подключает настоящие Telegram-сессии. Для запуска оператору нужна подготовленная среда Cluster с Podman, runsc и закреплённым образом Python:

python scripts/probe_workshop.py docker.io/library/python@sha256:ЗНАЧЕНИЕ_ОБРАЗА

Вместо ЗНАЧЕНИЕ_ОБРАЗА передаётся настоящий SHA256 установленного образа. Список успешных проверок выводится в конце. Код завершения, отличный от нуля, означает, что проверка не закончилась успешно.

Telegram и Bot API в этой программе заменены тестовыми ответами. Проверяются маршрутизация команды, создание формы, привязка нажатия, редактирование и ответ на кнопку. Доставка через настоящий Telegram проверяется отдельно. HTTPS-запросы идут к реальному внешнему сервису с вымышленными данными.

После прогона тестовые контейнеры, базы и файловые тома удаляются. Учебные модули не устанавливаются в рабочий юзербот и не появляются в общем каталоге.