Destiny
Destiny — декларативное описание желаемого состояния одного хоста: набор идемпотентных шагов вида core.<module>.<state>. Это атомарный кирпичик Soul Stack — «как привести один хост в нужное состояние», без оркестрации (на каких именно хостах и в каком порядке — это уже Scenario).
Destiny изолирован: он видит только свой объявленный вход (input:) и факты собственного хоста. Он не знает про базу, про вызвавший его сценарий и не подсматривает чужой контекст.
Раскладка
Заголовок раздела «Раскладка»Destiny — это папка в git-репозитории:
destiny-<name>/├── destiny.yml # манифест: input/output-контракты├── tasks/│ ├── main.yml # точка входа: список задач│ └── <sub>.yml # подключаемые соседи (include)├── templates/ # шаблоны файлов (.tmpl)└── vars.yml # локальные значения автора destinytasks/main.yml — это список задач, исполняемых сверху вниз. Никакой обёртки на верхнем уровне нет: путь к файлу сам сообщает контекст.
Минимальная задача адресует один модуль в форме core.<module>.<state> и передаёт параметры:
- name: Install redis-server package module: core.pkg.installed params: name: redis-serverИмя модуля трёхуровневое: core.pkg.installed читается как «пакет установлен» — это желаемое состояние, а не команда «установи». Доступные модули и их состояния — в каталоге core-модулей.
Идемпотентность
Заголовок раздела «Идемпотентность»Каждый шаг идемпотентен: модуль сначала проверяет текущее состояние хоста и меняет его, только если оно отличается от желаемого. Результат шага — changed=true (состояние поменялось) или changed=false (уже было таким). При повторном прогоне достигнутое состояние ничего не меняет.
changed-статус — это сигнал для зависимостей между шагами (например, «перезапусти сервис, только если изменился конфиг»).
Блоки задачи
Заголовок раздела «Блоки задачи»Помимо module: и params:, задача поддерживает блоки управления. Наиболее употребимые:
| Блок | Назначение |
|---|---|
name: | Человекочитаемое имя шага (идёт в лог применения). |
when: | Условие (CEL): задача выполняется, только если выражение истинно. |
register: | Имя, под которым сохраняется результат шага для последующих задач. |
vars: | Локальные значения, доступные внутри задачи. |
loop: | Повтор шага по элементам коллекции. |
changed_when: | Переопределяет, считать ли шаг изменившим состояние. |
failed_when: | Переопределяет, считать ли шаг упавшим. |
retry: / timeout: | Повторные попытки и лимит времени на попытку. |
no_log: | Скрыть параметры/результат шага из логов (для секретов). |
Зависимости между шагами (requisites)
Заголовок раздела «Зависимости между шагами (requisites)»Зависимости выражаются через register:-имена других задач:
| Блок | Семантика |
|---|---|
require: | Не стартовать до завершения упомянутых задач (барьер). |
onchanges: | Выполнить, только если упомянутая задача изменила состояние. |
onfail: | Выполнить, только если упомянутая задача упала (rescue/cleanup). |
Классический пример — перезапуск сервиса по изменению конфига:
- name: Render redis.conf module: core.file.present register: redis_conf params: path: /etc/redis/redis.conf content: "..."
- name: Restart redis-server because config changed module: core.service.restarted onchanges: [redis_conf] params: name: redis-serverПерезапуск произойдёт, только если рендер конфига сообщил changed=true.
Группировка и подключение
Заголовок раздела «Группировка и подключение»block:— inline-группа из нескольких задач с общим условиемwhen:: если оно ложно, пропускается вся группа разом, без повтора условия на каждой задаче.include:— подключение соседнего файла из той же папкиtasks/: его задачи вклеиваются на место include-задачи. Для переиспользуемых или больших групп.
Рендер файлов из шаблонов
Заголовок раздела «Рендер файлов из шаблонов»Файл, который собирается из шаблона, рендерит шаг core.file.rendered. Значения для шаблона явно поднимаются в vars: шага — это единственный канал «прокинуть данные в файл»:
- name: Render redis.conf from template module: core.file.rendered params: path: /etc/redis/redis.conf template: templates/redis.conf.tmpl vars: maxmemory: "${ essence.redis.maxmemory }" mode: "0640"Внутри templates/redis.conf.tmpl поднятое значение доступно как {{ .vars.maxmemory }}. Шаблон видит только то, что автор явно поднял, плюс узкий набор фактов хоста — не весь контекст. Подробнее про границу движков — в обзоре шаблонизатора.
Входной и выходной контракты
Заголовок раздела «Входной и выходной контракты»Destiny объявляет два опциональных контракта в destiny.yml — симметричных по форме:
input:— что Destiny принимает снаружи. Те, кто вызывает Destiny, обязаны передать значения по этому контракту; они валидируются (типы, обязательность,pattern/enum) до применения. В задачах вход доступен какinput.<имя>.output:— что Destiny публикует наружу как свой результат. Top-leveloutput:объявляет схему результата, а задачи заполняют объявленные поля через свой task-leveloutput:. Destiny публикует свой результат, но никогда не читает чужой контекст — изоляция при этом не нарушается.
input: version: type: string required: true maxmemory: type: stringoutput: config_path: type: stringЕсли Destiny ничего не возвращает, блок output: опускается — вызвавший всё равно получает стандартные changed / failed.
Шаблонный контекст
Заголовок раздела «Шаблонный контекст»В выражениях Destiny доступны:
| Имя | Содержание |
|---|---|
input.<имя> | Значения объявленного входного контракта. |
vars.<имя> | Локальные значения автора Destiny (vars.yml + task-level). |
soulprint.self.<путь> | Факты собственного хоста: soulprint.self.os.family, soulprint.self.network.primary_ip, … |
register.<имя>.* | Результаты предыдущих шагов: .changed, .failed и поля из их output:. |
Чего в контексте Destiny нет (это уровень сценария): прямого доступа к essence.*, к атрибутам вызывающего и кросс-хостовых запросов к фактам других хостов. Эти значения Destiny получает только через свой input:.
Полный пример
Заголовок раздела «Полный пример»- name: Install redis-server package module: core.pkg.installed retry: { count: 3, delay: 10s } # сеть может моргнуть params: name: redis-server version: "${ input.version }"
- name: Render redis.conf from template module: core.file.rendered register: redis_conf params: path: /etc/redis/redis.conf template: templates/redis.conf.tmpl vars: maxmemory: "${ input.maxmemory }" mode: "0640"
- name: Ensure redis-server is running and enabled at boot module: core.service.running params: name: redis-server enabled: true
- name: Restart redis-server because config changed module: core.service.restarted onchanges: [redis_conf] timeout: 30s params: name: redis-serverЧто дальше
Заголовок раздела «Что дальше»- Scenario — оркестрация Destiny по кластеру.
- Essence — откуда берутся значения параметров.
- Каталог core-модулей — что умеют встроенные модули.