# Ansible Integration Steward can browse, trigger, and stream output from Ansible playbooks directly from the web UI. Runs execute as asyncio tasks inside the same process — no Celery, no external workers. --- ## Playbook Sources Sources are configured at `/settings/` under Ansible. Two source types are supported: ### Local Filesystem Points to a directory on the host that already contains playbooks. ```yaml # In app_settings (configured via UI) ansible.sources: - name: "homelab" type: local path: "/opt/playbooks" ``` ### Git Repository The app clones or pulls the repo into a local cache directory on a configurable schedule. All execution uses the local cache. ```yaml ansible.sources: - name: "infra-repo" type: git url: "https://github.com/example/infra.git" branch: "main" pull_interval_seconds: 300 cache_path: "/data/playbook_cache/infra-repo" ``` Git sources register a `ScheduledTask` named `ansible_git_pull_` that runs `git pull` on the configured interval. --- ## Inventory Discovery The app discovers inventory files within the root of the playbook source directory (non-recursive). It looks for files named: - `hosts` - `inventory` - `inventory.yml` - `inventory.ini` A manual relative path can also be entered in the UI (e.g. `inventories/production/hosts`) for inventories in subdirectories. --- ## Triggering a Run From the UI at `/ansible/`, you can: 1. Browse available playbooks across all sources 2. View playbook contents before running 3. Select an inventory file 4. Trigger a run (requires `operator` or `viewer` role — execution is restricted to `operator`/`admin`) The run flow: 1. UI submits `POST /ansible/runs` with `playbook_path`, `source_name`, and `inventory_path` 2. An `AnsibleRun` row is created with `status = running` 3. Playbook execution starts as `asyncio.create_task()` 4. The response returns the `run_id` and an HTMX partial that wires the SSE subscription 5. The browser connects to `GET /ansible/runs//stream` to receive live output --- ## SSE Streaming Run output is streamed to the browser via Server-Sent Events (SSE) at: ``` GET /ansible/runs//stream ``` Each output line is sent as: ``` event: output data: ``` Run completion: ``` event: done data: success|failed|interrupted ``` Output is flushed to the `ansible_runs.output` DB column every 50 lines or every 5 seconds (whichever comes first) and always on completion. This means partial output survives a process crash. If a client connects after the run has already completed, the endpoint immediately sends `event: done` with the final status and closes the stream. To view stored output, use the run history view at `GET /ansible/runs/`. --- ## Run Lifecycle | Status | Description | |---|---| | `running` | Execution in progress | | `success` | Playbook exited 0 | | `failed` | Playbook exited non-zero | | `interrupted` | App restarted while run was in progress | On startup, the app marks any runs still in `running` state as `interrupted` (see `_mark_interrupted_runs()` in `app.py`). Output stored in the DB is capped at 1 MB. If truncated, `[output truncated]` is appended to the DB column, but the live SSE stream continues unaffected. --- ## Data Model `ansible_runs` table (defined in `steward/models/ansible.py`): | Column | Type | Description | |---|---|---| | `id` | UUID | Primary key | | `playbook_path` | str | Relative path to playbook | | `inventory_path` | str | Inventory path used | | `source_name` | str | Name of the playbook source | | `triggered_by` | FK → users | | | `status` | enum | `running`, `success`, `failed`, `interrupted` | | `started_at` | timestamp UTC | | | `finished_at` | timestamp UTC | Null if still running | | `output` | text | Captured stdout/stderr (capped at 1 MB) | Runs older than `data.retention_days` (default 90) are pruned by the `data_cleanup` scheduled task. --- ## Source Files | File | Purpose | |---|---| | `steward/ansible/sources.py` | Source discovery, git pull logic | | `steward/ansible/executor.py` | Subprocess execution and output streaming | | `steward/ansible/routes.py` | HTTP routes (browse, trigger, stream, history) | | `steward/models/ansible.py` | `AnsibleRun` model |