Files
ermnvldmr.com/services/docs/content/ru/apex/guides/action-development.md
T
deytenit b3638f4d63 fix: cyrillic fonts, dev certs, page head, and ru-text typography (#67)
🤖 PR generated for branch `fix/cyrillic-fonts-dev-certs-and-page-head`.

### Changes Summary
- **Fonts**: Add Cyrillic glyph subsets for Lato, EB Garamond, and Fira
Code.
- **Dev**: Support remote Root CA and encrypted dev CA key in
`.env.dev`.
- **UI & Kits**: Add size variant to `Breadcrumbs` component and enlarge
`PageHead` breadcrumbs with tuned collapse scroll threshold.
- **ESLint**: Add `no-hanging-particles` i18n & markdown typography rule
with auto-fixer.
- **Content & i18n**: Apply `ru-text` editorial and typography polish
(em dashes, number ranges, «ё» restoration, non-breaking spaces) across
`services/www` and `services/docs`.
2026-10-25 15:21:09 +03:00

6.7 KiB
Raw Blame History

title, description, weight
title description weight
Разработка действий Создавайте, переопределяйте и тестируйте действия движка в commons или proprietaries. 22

Контекст: вся автоматизация устроена в виде действий — по одному Python-модулю на каждое действие, диспетчеризуемых по пути к файлу. Общий код находится в каталоге commons actions/ («commons» — общий репозиторий), а специфика конкретного узла — в его proprietaries/actions/. Оба дерева используют единый контракт, а запускатель добавляет оба каталога в sys.path. Поэтому локальное действие импортирует engine.* точно так же, как и действие из commons.

Минимальное действие

"""Однострочный docstring модуля, описывающий действие."""
import os
from engine.descriptor import Meta, Arg, Flag

METADATA = Meta(
    summary="Скопировать отрендеренную конфигурацию на место.",
    args=[Arg("target_dir", "Каталог назначения"),
          Flag("--dry-run", "Только предпросмотр.")],
)

def run(ctx, args):
    if not os.path.isdir(args.target_dir):
        ctx.log.error(f"Нет такого каталога: {args.target_dir}")
        raise SystemExit(66)
    content = ctx.tpl.render("example/config", ctx.vars())
    ctx.host.write_file(os.path.join(args.target_dir, "config"), content,
                        dry_run=args.dry_run, sudo=True)
    ctx.log.success("Конфигурация развёрнута.")

Путь к файлу задаёт имя действия: модуль выше вызывается как apex utils/example. Верните 0 или None при успехе, целое число — для конкретного кода выхода, либо выбросьте SystemExit(code). Соглашение о кодах выхода: 64 — ошибка использования, 65 — ошибка данных, 66 — отсутствие входных данных, 1 — общая ошибка.

Соглашения для фоновых действий

Действия, которые запускаются по cron без надзора оператора, следуют двум правилам, чтобы сбои отправляли оповещение, а не исчезали бесследно:

  • Оберните тело в try/except Exception и отправьте ctx.notify.error(...) перед тем как выбросить SystemExit(1) — это Python-аналог перехвата ERR в shell.
  • Привяжите код выхода к итоговому уведомлению: методы ctx.notify.* возвращают False, если доставить сообщение не удалось. Запуск, чьё статусное сообщение не дошло, всё равно должен завершиться с кодом 1.
    try:
        ...работа...
        log.success("Готово.")
        if not ctx.notify.success(TITLE, url, "Готово.", node):
            raise SystemExit(1)
    except SystemExit:
        raise
    except Exception as e:
        log.error(f"{type(e).__name__}: {e}")
        ctx.notify.error(TITLE, url, f"Критическая ошибка: {e}", node)
        raise SystemExit(1)

Переопределение, обёртывание, отключение

Проприетарный файл с тем же относительным путём, что и действие из commons, полностью перекрывает его. Внутри переопределяющего модуля вызов ctx.commons.run(args) обращается к исходной реализации из commons — по паттерну обёртки:

from engine.descriptor import Meta, Arg

METADATA = Meta(summary="sync/packages с локальным для узла предварительным шагом.",
                args=[Arg("telegram_bot_url", "Telegram Bot URL")])

def run(ctx, args):
    ctx.log.info("Локальная для узла подготовка...")
    return ctx.commons.run(args)          # делегирование реализации из commons

Чтобы отключить действие на конкретном узле, укажите DISABLED = "reason" на верхнем уровне модуля. Диспетчер отклонит запуск такого действия с кодом выхода 1, а в общем списке появится отметка [DISABLED: reason].

Тестирование

Набор тестов commons построен на стандартном модуле unittest:

./tests/run.sh          # python3 -m unittest discover -s tests -p 'test_*.py' -v

Изменения в движке должны сохранять тесты зелёными. Быстрая smoke-проверка для любого действия (как из commons, так и проприетарного) — запуск со справкой: команда apex <name> --help должна завершаться с кодом 0. Это подтверждает, что модуль синтаксически корректен, объект METADATA сформирован без ошибок, а механизм обнаружения находит действие.

Хук pre-commit в commons (скрипт init.sh подключает его через core.hooksPath) запускает apex utils/lint-docker-compose --hook для подготовленных (staged) compose-файлов, поэтому ошибки в композициях отлавливаются ещё на этапе коммита.


См. также: