feat(plugins): plugin capability registry + host_agent→Ansible deploy synergy
Implements #253's framework: a small core capability registry (steward/core/capabilities.py) where a module/plugin publishes a named, role-gated action and a consumer discovers it via has_capability() and runs it via invoke_capability() — no hard import, graceful degradation, permission propagation (actor role checked against the capability's required_role). Core publishes "ansible.run_playbook" (operator) wrapping ansible.runner. trigger_run (extended to accept a caller-built inventory). First consumer: the host_agent plugin gains "Deploy via Ansible" on its settings page — pick an inventory target/group and it installs/updates the agent via the bundled host_agent/install.yml, minting a fresh token per host and injecting it as an inventory hostvar (turning per-host curl|sh into one run). Exposed role ordering as middleware.role_meets. Unit tests for the registry + role checks. Task #253 (milestone #37). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,10 +24,14 @@ async def trigger_run(
|
||||
inventory_scope: str = "steward:all",
|
||||
params: dict | None = None,
|
||||
triggered_by: str | None = None,
|
||||
inventory_content: str | None = None,
|
||||
):
|
||||
"""Resolve inventory for the scope, persist an AnsibleRun, and launch it.
|
||||
|
||||
triggered_by=None marks a system/automated run (alerts, schedules).
|
||||
If inventory_content is provided, it is used verbatim and scope resolution
|
||||
is skipped (the caller built a bespoke inventory — e.g. host_agent deploy
|
||||
injecting per-host tokens); inventory_scope is still recorded for display.
|
||||
Returns (run, source, error): on success error is None; on failure run is
|
||||
None and error is a short human-readable reason.
|
||||
"""
|
||||
@@ -36,9 +40,10 @@ async def trigger_run(
|
||||
if source is None:
|
||||
return None, None, "Source not found"
|
||||
|
||||
inventory_content: str | None = None
|
||||
inventory_path: str | None = None
|
||||
if inventory_scope.startswith("steward:"):
|
||||
if inventory_content is not None:
|
||||
pass # caller-supplied inventory wins; scope kept only for display
|
||||
elif inventory_scope.startswith("steward:"):
|
||||
async with app.db_sessionmaker() as db:
|
||||
targets = await fetch_scope_targets(db, inventory_scope)
|
||||
inventory_content = json.dumps(generate_inventory(targets))
|
||||
|
||||
Reference in New Issue
Block a user