Как написать SoulModule
SoulModule — плагин, который реализует один шаг Destiny: приводит ресурс на хосте к нужному состоянию. Исполняемый файл называется soul-mod-<имя>, запускает его soul-агент. Этот раздел — про то, как написать такой модуль; общая модель плагинов — в обзоре расширения.
Сначала проверьте, нет ли нужного в каталоге core-модулей: пакеты, файлы, сервисы, пользователи, cron, монтирование, firewall, git, архивы, sysctl уже покрыты встроенными модулями. SoulModule-плагин нужен для интеграций, которых нет в core — например конкретная СУБД, reverse-proxy, контейнер-рантайм.
Контракт: что реализует модуль
Заголовок раздела «Контракт: что реализует модуль»Модуль реализует интерфейс из SDK. Два ключевых метода:
Validate— проверяет входные параметры шага (без побочных эффектов). Невалидный ввод отклоняется до применения. Тот же контракт даётsoul-lint-у статическую проверку через манифест.Apply— приводит ресурс к желаемому состоянию и сообщает, что изменилось.
Это тот же интерфейс, что реализуют встроенные core-модули, — плагин и core-модуль для агента неотличимы.
Идемпотентность — обязательна
Заголовок раздела «Идемпотентность — обязательна»Apply должен быть идемпотентным: сначала прочитать текущее состояние ресурса и менять его, только если оно отличается от желаемого. Результат шага — флаг changed:
changed = true— состояние ресурса было изменено этим запуском.changed = false— ресурс уже был в нужном состоянии, ничего не делалось.
Этот флаг — не косметика: на нём строятся зависимости между шагами (например onchanges: — «перезапусти сервис, только если поменялся конфиг»). Модуль, который всегда возвращает changed = true, ломает эту механику и провоцирует лишние перезапуски. Подробнее про идемпотентность и changed — в разделе про Destiny.
Естественная семантика состояний — по ресурсу: для постоянных ресурсов это present / absent, для процессов и контейнеров — running / stopped / absent. Навязывать всем present/absent не нужно — выбирайте состояния, отражающие природу ресурса.
Манифест
Заголовок раздела «Манифест»Рядом с исполняемым файлом — статический manifest.yaml. Он описывает плагин так, чтобы soul-lint мог валидировать Destiny без запуска плагина:
kind: soul_module # тип плагинаprotocol_version: 1 # версия plugin-протокола (compat-флаг, не версия модуля)namespace: acme # пространство имён автораname: pgrole # имя модуляrequired_capabilities: [network_outbound] # что нужно от host-аside_effects: # какие ресурсы трогает - { service: postgresql }spec: states: present: # поддерживаемые состояния и их параметры input: name: { type: string, required: true } password: { type: string, secret: true } absent: input: name: { type: string, required: true }kind: soul_module— дискриминатор типа плагина.protocol_version— версия протокола плагинов, compat-флаг. Это не версия вашего модуля (версия модуля — git-ref релиза). Host поддерживает фиксированный набор версий протокола; при несовпадении запуск отклоняется. Менять это число нужно только при смене формата протокола, а не при каждом релизе модуля.required_capabilities— что плагину нужно от host-а (например исходящая сеть, доступ к Vault, запуск под root).soul-lintстатически проверяет, что запрошенное укладывается в политику host-а, и валит Destiny до применения, если нет.side_effects— все ресурсы, которые модуль трогает (сервисы, файлы, пакеты, порты и т.д.). Это и audit-trail, и обнаружение конфликтов: если два модуля в одном прогоне претендуют на один ресурс — host об этом сигнализирует. Если модуль в рантайме трогает ресурс, не объявленный вside_effects, — шаг помечается как нарушивший политику.spec.states— поддерживаемые состояния и схема параметров каждого. По нейsoul-lintпроверяет параметры шага Destiny.
Манифест и код модуля должны быть согласованы — это ответственность автора (помогает self-test: вызов Apply с параметрами вне схемы должен вернуть ошибку валидации аргумента).
Сборка и подключение
Заголовок раздела «Сборка и подключение»-
Сгенерируйте каркас командой
soul-lint plugin-init:Окно терминала soul-lint plugin-init acme/pgrole --out ./soul-mod-pgrole --author "Acme Ops"Это даёт готовое дерево проекта с манифестом и заглушкой обработчика — остаётся написать
Apply. -
Реализуйте
Validate/Applyчерез интерфейс SDK и соберите исполняемый файлsoul-mod-<имя>. Поскольку плагин — отдельный процесс, общающийся по gRPC, язык реализации не навязывает рантайм-зависимость управляемому хосту: на хост приходит самодостаточный исполняемый файл. -
Проверьте манифест локально:
Окно терминала soul-lint validate-manifest ./soul-mod-pgrole/manifest.yaml
Как агент подхватывает модуль
Заголовок раздела «Как агент подхватывает модуль»Плагины регистрируются у Keeper-а (источник + git-ref); Keeper резолвит их в исполняемые файлы и держит в artifact-кеше. Доставка на хост и кеширование устроены так:
- На хосте исполняемые файлы и модули лежат в локальном кеше; имя файла включает SHA-256 содержимого, поэтому рядом могут лежать несколько версий, а доставка идёт только при реальном изменении.
- В push-режиме Keeper передаёт хосту все зарегистрированные модули скопом (без анализа конкретного Destiny) и для каждого сравнивает SHA-256 с тем, что уже в кеше: совпало — копирование пропускается, изменилось — докачивается. Сам исполняемый файл
soulобновляется тем же механизмом сравнения по SHA-256. - В pull-режиме модуль доставляется обычной Destiny-операцией, кладётся в локальный кеш и верифицируется по фингерпринту.
При исполнении шага агент запускает закешированный исполняемый файл как дочерний процесс, проводит handshake и вызывает Apply по gRPC. Подробнее про хостовый кеш и доставку — в компоненте Soul.
Что дальше
Заголовок раздела «Что дальше»- Обзор расширения — модель плагинов и SDK.
- CloudDriver и SshProvider — серверные плагины.
- Каталог core-модулей — эталоны идемпотентного поведения.
- soul-lint — scaffold и валидация.