fix, feat, doc: massive build system refactorion, bug fixes and apex documentation (#19)
This commit is contained in:
1 parent
e8b42c60c2
commit
91f5245823
165 files changed
+8598
-1205
No files matched your search
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: Develop actions
|
||||
description: Write, override, and test engine actions in commons or proprietaries.
|
||||
weight: 22
|
||||
---
|
||||
|
||||
**Context:** all automation ships as [actions](/apex/glossary#action) — one Python module each, dispatched by path. Shared behavior belongs in [commons](/apex/glossary#commons) `actions/`; anything node-specific belongs in that node's `proprietaries/actions/`. Both use the identical contract, and the launcher puts both trees on `sys.path`, so a proprietary action imports `engine.*` exactly like a commons one.
|
||||
|
||||
## A minimal action {#minimal}
|
||||
|
||||
```python {filename="proprietaries/actions/utils/example.py"}
|
||||
"""One-line module docstring describing the action."""
|
||||
import os
|
||||
from engine.descriptor import Meta, Arg, Flag
|
||||
|
||||
METADATA = Meta(
|
||||
summary="Copy a rendered config into place.",
|
||||
args=[Arg("target_dir", "Destination directory"),
|
||||
Flag("--dry-run", "Preview only.")],
|
||||
)
|
||||
|
||||
def run(ctx, args):
|
||||
if not os.path.isdir(args.target_dir):
|
||||
ctx.log.error(f"No such directory: {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("Config deployed.")
|
||||
```
|
||||
|
||||
The file's path is its name: the module above dispatches as `apex utils/example`. Return `0`/`None` for success, an integer for a specific exit code, or raise `SystemExit(code)`; the [exit-code convention](/apex/concepts/actions-and-overlay#exit-codes) is `64` usage, `65` data error, `66` missing input, `1` general.
|
||||
|
||||
## Conventions for unattended actions {#unattended}
|
||||
|
||||
Actions that run from cron follow two rules so failures page instead of vanishing:
|
||||
|
||||
- **Wrap the body** in `try/except Exception` and send `ctx.notify.error(...)` before re-raising as `SystemExit(1)` — the Python equivalent of a shell ERR trap.
|
||||
- **Gate the exit code on the final notification**: `ctx.notify.*` returns `False` on delivery failure; a completed run whose status message never arrived should still exit `1`.
|
||||
|
||||
```python
|
||||
try:
|
||||
...work...
|
||||
log.success("Done.")
|
||||
if not ctx.notify.success(TITLE, url, "Done.", node):
|
||||
raise SystemExit(1)
|
||||
except SystemExit:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"{type(e).__name__}: {e}")
|
||||
ctx.notify.error(TITLE, url, f"Critical error: {e}", node)
|
||||
raise SystemExit(1)
|
||||
```
|
||||
|
||||
## Override, wrap, disable {#override}
|
||||
|
||||
A proprietary file at the same relative path as a commons action shadows it completely. Inside the shadowing module, `ctx.commons.run(args)` invokes the shadowed implementation — the wrap pattern:
|
||||
|
||||
```python {filename="proprietaries/actions/sync/packages.py"}
|
||||
from engine.descriptor import Meta, Arg
|
||||
|
||||
METADATA = Meta(summary="sync/packages with a node-local pre-step.",
|
||||
args=[Arg("telegram_bot_url", "Telegram Bot URL")])
|
||||
|
||||
def run(ctx, args):
|
||||
ctx.log.info("Node-local preparation...")
|
||||
return ctx.commons.run(args) # delegate to the commons implementation
|
||||
```
|
||||
|
||||
To retire an action on one node, assign `DISABLED = "reason"` at module top level: dispatch refuses it (exit `1`) and the listing shows `[DISABLED: reason]`.
|
||||
|
||||
## Testing {#testing}
|
||||
|
||||
The commons test suite is stdlib `unittest`:
|
||||
|
||||
```bash
|
||||
./tests/run.sh # python3 -m unittest discover -s tests -p 'test_*.py' -v
|
||||
```
|
||||
|
||||
Engine changes must keep the suite green. A cheap smoke gate for any action — commons or proprietary — is its parser: `apex <name> --help` must exit `0`, which proves the module parses, its `METADATA` is well-formed, and discovery sees it.
|
||||
|
||||
The commons pre-commit hook (wired by `init.sh` via `core.hooksPath`) runs `apex utils/lint-docker-compose --hook` against staged compose files, so composition changes are linted at commit time.
|
||||
|
||||
---
|
||||
|
||||
**See also:**
|
||||
|
||||
- [Concept: Actions and the overlay](/apex/concepts/actions-and-overlay)
|
||||
- [Concept: The execution context](/apex/concepts/execution-context)
|
||||
- [Reference: Engine API](/apex/reference/engine-api)
|
||||
Reference in new issue
Block a user