init(opencode-ansible): scaffold dedicated Ansible repo for opencode propagation
This commit is contained in:
commit
991be3dd90
51
README.md
Normal file
51
README.md
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# OpenCode Ansible
|
||||||
|
|
||||||
|
Dedicated Ansible orchestration repo for propagating opencode (oh-my-openagent) across the fleet.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
opencode-ansible/
|
||||||
|
├── ansible.cfg # Ansible configuration
|
||||||
|
├── inventory/
|
||||||
|
│ ├── opencode.yml # Main inventory
|
||||||
|
│ └── group_vars/
|
||||||
|
│ └── opencode.yml # Group-wide defaults
|
||||||
|
├── host_vars/ # Per-host overrides
|
||||||
|
├── playbooks/ # Deployment playbooks
|
||||||
|
├── roles/ # Ansible roles (if needed)
|
||||||
|
├── templates/ # Jinja2 templates
|
||||||
|
├── scripts/ # CLI wrapper scripts
|
||||||
|
├── callback_plugins/ # Audit logging
|
||||||
|
├── audit/ # JSONL audit trail
|
||||||
|
└── docs/ # Documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check readiness of a host
|
||||||
|
ansible-playbook -i inventory/opencode.yml playbooks/readiness.yml --limit crew2
|
||||||
|
|
||||||
|
# Full deployment pipeline
|
||||||
|
bash scripts/full-deploy.sh --limit crew2
|
||||||
|
|
||||||
|
# Verify deployment
|
||||||
|
ansible-playbook -i inventory/opencode.yml playbooks/verify.yml --limit crew2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Playbooks
|
||||||
|
|
||||||
|
| Playbook | Purpose |
|
||||||
|
|----------|---------|
|
||||||
|
| `readiness.yml` | Pre-install host assessment |
|
||||||
|
| `bootstrap.yml` | Install prerequisites and service accounts |
|
||||||
|
| `install.yml` | Install opencode binary/runtime |
|
||||||
|
| `config.yml` | Deploy configuration and sync repos |
|
||||||
|
| `service.yml` | Deploy and enable systemd services |
|
||||||
|
| `verify.yml` | Post-deployment health checks |
|
||||||
|
| `rollback.yml` | Revert opencode deployment |
|
||||||
|
|
||||||
|
## Target Hosts
|
||||||
|
|
||||||
|
crew2, crewsupport, llm01, erpnext, poweredget150, a10, noc, kenny-GE76-Raider-11UE
|
||||||
33
ansible.cfg
Normal file
33
ansible.cfg
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
[defaults]
|
||||||
|
# Inventory
|
||||||
|
inventory = inventory/opencode.yml
|
||||||
|
|
||||||
|
# Connection
|
||||||
|
host_key_checking = False
|
||||||
|
timeout = 30
|
||||||
|
|
||||||
|
# Output
|
||||||
|
stdout_callback = yaml
|
||||||
|
bin_ansible_callbacks = True
|
||||||
|
|
||||||
|
# Callback plugins (audit logging)
|
||||||
|
callback_plugins = callback_plugins
|
||||||
|
callbacks_enabled = audit_log
|
||||||
|
|
||||||
|
# Retry files — disable, use audit log instead
|
||||||
|
retry_files_enabled = False
|
||||||
|
|
||||||
|
# Forks — fleet is small, keep low
|
||||||
|
forks = 5
|
||||||
|
|
||||||
|
# Logging (in addition to audit callback)
|
||||||
|
log_path = audit/ansible.log
|
||||||
|
|
||||||
|
# Vault — reference existing netman vault password file
|
||||||
|
vault_password_file = /home/kenny/nc/ansible/.ansible/vault_pass.txt
|
||||||
|
|
||||||
|
[privilege_escalation]
|
||||||
|
become = False
|
||||||
|
|
||||||
|
[ssh_connection]
|
||||||
|
pipelining = True
|
||||||
0
audit/.gitkeep
Normal file
0
audit/.gitkeep
Normal file
107
callback_plugins/audit_log.py
Normal file
107
callback_plugins/audit_log.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# audit_log.py — Ansible callback plugin for fleet-ops audit trail
|
||||||
|
# Logs each playbook run to /audit/<date>-<playbook>.jsonl
|
||||||
|
# Format: one JSON object per line (JSONL), one file per playbook per day
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
from ansible.plugins.callback import CallbackBase
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
name: audit_log
|
||||||
|
type: notification
|
||||||
|
short_description: Write structured audit log to /audit/
|
||||||
|
description:
|
||||||
|
- Appends a JSON record for each playbook run to /audit/<date>-<playbook>.jsonl
|
||||||
|
- Fields: timestamp, completed, playbook, check_mode, outcome, summary, tasks
|
||||||
|
options: {}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackModule(CallbackBase):
|
||||||
|
CALLBACK_VERSION = 2.0
|
||||||
|
CALLBACK_TYPE = "notification"
|
||||||
|
CALLBACK_NAME = "audit_log"
|
||||||
|
CALLBACK_NEEDS_WHITELIST = True
|
||||||
|
|
||||||
|
AUDIT_DIR = "/audit"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._playbook_name = "unknown"
|
||||||
|
self._start_time = None
|
||||||
|
self._check_mode = False
|
||||||
|
self._task_results: list[dict] = []
|
||||||
|
|
||||||
|
def v2_playbook_on_start(self, playbook):
|
||||||
|
self._playbook_name = os.path.basename(playbook._file_name)
|
||||||
|
self._start_time = datetime.now(timezone.utc).isoformat()
|
||||||
|
self._task_results = []
|
||||||
|
|
||||||
|
def v2_playbook_on_play_start(self, play):
|
||||||
|
self._check_mode = play.check_mode
|
||||||
|
|
||||||
|
def _record(self, result, status: str):
|
||||||
|
entry = {
|
||||||
|
"host": result._host.name,
|
||||||
|
"task": result._task.get_name(),
|
||||||
|
"status": status,
|
||||||
|
}
|
||||||
|
if status == "failed":
|
||||||
|
entry["msg"] = str(result._result.get("msg", ""))
|
||||||
|
self._task_results.append(entry)
|
||||||
|
|
||||||
|
def v2_runner_on_ok(self, result):
|
||||||
|
self._record(result, "ok")
|
||||||
|
|
||||||
|
def v2_runner_on_failed(self, result, ignore_errors=False):
|
||||||
|
self._record(result, "failed" if not ignore_errors else "ignored")
|
||||||
|
|
||||||
|
def v2_runner_on_unreachable(self, result):
|
||||||
|
self._record(result, "unreachable")
|
||||||
|
|
||||||
|
def v2_runner_on_skipped(self, result):
|
||||||
|
self._record(result, "skipped")
|
||||||
|
|
||||||
|
def v2_playbook_on_stats(self, stats):
|
||||||
|
end_time = datetime.now(timezone.utc).isoformat()
|
||||||
|
|
||||||
|
summary = {}
|
||||||
|
for host in stats.processed:
|
||||||
|
summary[host] = {
|
||||||
|
"ok": stats.ok.get(host, 0),
|
||||||
|
"changed": stats.changed.get(host, 0),
|
||||||
|
"failed": stats.failures.get(host, 0),
|
||||||
|
"unreachable": stats.dark.get(host, 0),
|
||||||
|
"skipped": stats.skipped.get(host, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
any_failed = any(
|
||||||
|
v["failed"] > 0 or v["unreachable"] > 0 for v in summary.values()
|
||||||
|
)
|
||||||
|
outcome = "failed" if any_failed else "success"
|
||||||
|
if self._check_mode:
|
||||||
|
outcome = f"check-{outcome}"
|
||||||
|
|
||||||
|
log_entry = {
|
||||||
|
"timestamp": self._start_time,
|
||||||
|
"completed": end_time,
|
||||||
|
"playbook": self._playbook_name,
|
||||||
|
"check_mode": self._check_mode,
|
||||||
|
"outcome": outcome,
|
||||||
|
"summary": summary,
|
||||||
|
"tasks": self._task_results,
|
||||||
|
}
|
||||||
|
|
||||||
|
os.makedirs(self.AUDIT_DIR, exist_ok=True)
|
||||||
|
date_str = (self._start_time or end_time)[:10]
|
||||||
|
playbook_stem = self._playbook_name.replace(".yml", "").replace(".yaml", "")
|
||||||
|
log_file = os.path.join(self.AUDIT_DIR, f"{date_str}-{playbook_stem}.jsonl")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(log_file, "a") as f:
|
||||||
|
f.write(json.dumps(log_entry) + "\n")
|
||||||
|
except OSError as e:
|
||||||
|
self._display.warning(f"audit_log: could not write to {log_file}: {e}")
|
||||||
0
docs/.gitkeep
Normal file
0
docs/.gitkeep
Normal file
0
host_vars/.gitkeep
Normal file
0
host_vars/.gitkeep
Normal file
3
inventory/group_vars/opencode.yml
Normal file
3
inventory/group_vars/opencode.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
# OpenCode Propagation: Group-wide Defaults
|
||||||
|
# TODO: Implement in Task 2
|
||||||
3
inventory/opencode.yml
Normal file
3
inventory/opencode.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
# OpenCode Propagation: Main Inventory
|
||||||
|
# TODO: Implement in Task 2
|
||||||
4
playbooks/bootstrap.yml
Normal file
4
playbooks/bootstrap.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
# OpenCode Propagation: Bootstrap Prerequisites
|
||||||
|
# Installs prerequisites (Python, Node.js, service accounts, etc.)
|
||||||
|
# TODO: Implement in Task 8
|
||||||
4
playbooks/config.yml
Normal file
4
playbooks/config.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
# OpenCode Propagation: Config and Repo Sync
|
||||||
|
# Deploys configuration and syncs Gitea repos on target hosts
|
||||||
|
# TODO: Implement in Task 10
|
||||||
4
playbooks/install.yml
Normal file
4
playbooks/install.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
# OpenCode Propagation: Install OpenCode
|
||||||
|
# Installs opencode binary/runtime on target hosts
|
||||||
|
# TODO: Implement in Task 9
|
||||||
4
playbooks/readiness.yml
Normal file
4
playbooks/readiness.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
# OpenCode Propagation: Readiness Assessment
|
||||||
|
# Verifies target hosts are ready for opencode installation
|
||||||
|
# TODO: Implement in Task 7
|
||||||
5
playbooks/rollback.yml
Normal file
5
playbooks/rollback.yml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
# OpenCode Propagation: Rollback
|
||||||
|
# Reverts opencode deployment to clean state
|
||||||
|
# Requires --extra-vars "confirm_rollback=yes" to run
|
||||||
|
# TODO: Implement in Task 15
|
||||||
4
playbooks/service.yml
Normal file
4
playbooks/service.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
# OpenCode Propagation: Service Deployment
|
||||||
|
# Deploys and enables systemd user services for opencode
|
||||||
|
# TODO: Implement in Task 11
|
||||||
4
playbooks/verify.yml
Normal file
4
playbooks/verify.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
# OpenCode Propagation: Verification and Health Checks
|
||||||
|
# Post-deployment verification and health checks
|
||||||
|
# TODO: Implement in Task 12
|
||||||
0
roles/.gitkeep
Normal file
0
roles/.gitkeep
Normal file
0
scripts/.gitkeep
Normal file
0
scripts/.gitkeep
Normal file
0
templates/.gitkeep
Normal file
0
templates/.gitkeep
Normal file
Loading…
x
Reference in New Issue
Block a user