Перейти к содержимому

Как написать 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 с параметрами вне схемы должен вернуть ошибку валидации аргумента).

  1. Сгенерируйте каркас командой soul-lint plugin-init:

    Окно терминала
    soul-lint plugin-init acme/pgrole --out ./soul-mod-pgrole --author "Acme Ops"

    Это даёт готовое дерево проекта с манифестом и заглушкой обработчика — остаётся написать Apply.

  2. Реализуйте Validate / Apply через интерфейс SDK и соберите исполняемый файл soul-mod-<имя>. Поскольку плагин — отдельный процесс, общающийся по gRPC, язык реализации не навязывает рантайм-зависимость управляемому хосту: на хост приходит самодостаточный исполняемый файл.

  3. Проверьте манифест локально:

    Окно терминала
    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.