--- title: "Действия и оверлей" description: "Контракт действия и затенение действий commons локальными проприетариями узла." weight: 13 --- Каждая единица автоматизации в apex — это [действие](/apex/glossary#action): один Python-модуль, который [движок](/apex/glossary#engine) диспетчеризует по имени. [Оверлей](/apex/glossary#overlay) позволяет [проприетариям](/apex/glossary#proprietaries) узла добавлять, переопределять или отключать действия [«commons»](/apex/glossary#commons) без создания форков. На этой странице описаны контракт действия, декларативные дескрипторы аргументов и правила разрешения. ## Контракт действия {#contract} Действие — это отдельный файл `.py` в дереве `actions/`. Путь к файлу служит именем действия: `actions//.py` вызывается как `apex / [args...]`. Обновлять реестр не нужно: появление файла в дереве сразу публикует действие. Модуль должен предоставлять две вещи: - **`METADATA = Meta(...)`** → краткое описание и дескрипторы аргументов, управляющие как текстом справки, так и парсингом. Система дескрипторов пришла на смену heredoc-блоку `action_metadata()` bash-фреймворка «root» commons, предшественника apex. - **`run(ctx, args)`** → точка входа. `ctx` — сформированный движком контекст выполнения; `args` — распарсенное пространство имён `argparse`. ```python {filename="actions//.py"} from engine.descriptor import Meta, Arg, Flag METADATA = Meta( summary="Однострочное описание, показываемое в перечислении действий", args=[ Arg("target", help="Обязательный позиционный аргумент"), Flag("--dry-run", help="Булев флаг"), ], ) def run(ctx, args): ... return 0 ``` Возвращаемое значение `run` становится кодом завершения процесса. Нецелочисленный результат (включая `None`) трактуется как успешное завершение (`0`). Необработанное исключение логируется с префиксом контекста, после чего процесс завершается с кодом `1`. > [!NOTE] > Технически `METADATA` необязательна: модуль без неё диспетчеризуется с пустым `Meta` и не принимает аргументов. Однако в реальных действиях её объявлять необходимо, поскольку вывод перечисления и справка `--help` генерируются именно из неё. ## Декларативные аргументы {#arguments} `Meta.args` — это список дескрипторов. Каждый отображается ровно на одну конструкцию `argparse`: | Дескриптор | Отображается на | Поведение | | --- | --- | --- | | `Arg("name")` | позиционный аргумент | Обязателен по умолчанию; `required=False` делает его опциональным позиционным (`nargs="?"`, по умолчанию `None`). | | `Opt("--name", default=...)` | опция со значением | Опциональный флаг, принимающий значение, с объявленным значением по умолчанию. | | `Flag("--name")` | булев флаг | `store_true`; наличие означает `True`. | | `Rest("name")` | остаток | Жадная сквозная передача всех оставшихся аргументов командной строки (`argparse.REMAINDER`). | Движок строит парсер как `apex /` и разбирает оставшиеся аргументы перед вызовом `run`. Флаг `--help` обрабатывается на этапе парсинга. При ошибке использования (неизвестный флаг, отсутствующий позиционный аргумент) выводится справка по синтаксису (usage), а процесс завершается с кодом `64` (`EX_USAGE`) вместо стандартного для argparse кода `2`. ## Перечисление без импорта {#listing} Команда `apex` без аргументов (или с флагом `--help`) выводит список всех доступных действий. Перечисление никогда не импортирует модули действий: движок статически сканирует каждый файл с помощью модуля `ast` и извлекает только краткое описание `METADATA` и любую константу `DISABLED`. Поэтому код действия не выполняется и не может вызвать сбой во время перечисления: файл, который не удалось распарсить, помечается как `(unparseable)`, не нарушая работу всей команды. Каждая запись помечается своим источником оверлея: - нет метки → действие commons; - `(local)` → существует только в проприетариях; - `(local override)` → действие проприетариев, затеняющее действие commons; - `[DISABLED: reason]` → действие объявляет константу `DISABLED`. ## Оверлей {#overlay} Имена действий разрешаются по двум деревьям, начиная с проприетариев: {{< filetree/container >}} {{< filetree/folder name="node repository" state="open" >}} {{< filetree/file name="node.env" >}} {{< filetree/folder name="commons" state="open" >}} {{< filetree/folder name="engine" state="closed" >}}{{< /filetree/folder >}} {{< filetree/folder name="actions" state="open" >}} {{< filetree/file name="/.py" >}} {{< /filetree/folder >}} {{< /filetree/folder >}} {{< filetree/folder name="proprietaries" state="open" >}} {{< filetree/folder name="actions" state="open" >}} {{< filetree/file name="/.py" >}} {{< /filetree/folder >}} {{< /filetree/folder >}} {{< /filetree/folder >}} {{< /filetree/container >}} Движок определяет структуру размещения по своему расположению на диске: если запуск происходит из каталога `commons/`, а в родительском каталоге присутствует `node.env`, родительский каталог считается корнем репозитория [узла](/apex/glossary#node), а `proprietaries/actions/` — деревом оверлея. Автономный checkout commons сам выступает в качестве корня и не имеет слоя оверлея. Внутри репозитория узла [идентичность узла](/apex/glossary#node-identity) разрешается из FQDN хоста перед диспетчеризацией. При разрешении имени движок сначала проверяет `proprietaries/actions/.py`, а затем `commons/actions/.py`; по найденному пути загружается ровно один модуль. Неизвестное имя считается ошибкой (код завершения `1`). Оверлей поддерживает четыре сценария: - **Добавление (Add)** → файл, существующий только в проприетариях, становится локальным действием узла и отображается в списке как `(local)`. - **Переопределение (Override)** → файл проприетариев по тому же относительному пути полностью затеняет реализацию commons; путь commons помечается при разрешении как `shadowed`. - **Обёртывание (Wrap)** → переопределяющий модуль может делегировать вызов затенённой реализации через `ctx.commons.run(args)`, дополняя поведение commons локальной логикой узла до или после её вызова. - **Отключение (Disable)** → модуль с объявлением `DISABLED = "reason"` отклоняется при диспетчеризации: движок логирует причину и завершается с кодом `1`. При перечислении выводится `[DISABLED: reason]`. Простое значение `DISABLED = True` также отключает действие со стандартной причиной `disabled`; пустая строка действие не отключает. ## Коды завершения {#exit-codes} Действия и движок следуют единому соглашению о кодах завершения: | Код | Значение | Типичная причина | | --- | --- | --- | | `0` | успех | Функция `run` вернула `0` или нецелочисленное значение. | | `1` | общая ошибка | Неизвестное действие, отключённое действие, необработанное исключение, нецелочисленный `SystemExit`. | | `64` | ошибка использования | Аргументы отклонены парсером (`EX_USAGE`). | | `65` | ошибка данных | Некорректные входные данные (`EX_DATAERR`), выбрасывается библиотеками движка. | | `66` | отсутствующий ввод | Требуемый ввод отсутствует (`EX_NOINPUT`), выбрасывается библиотеками движка. | --- **См. также:** - [Концепция: Архитектура фреймворка](/apex/concepts/architecture) - [Концепция: Контекст выполнения](/apex/concepts/execution-context) - [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com)