Skip to main content

convergio_provisioning/
ext.rs

1//! Extension trait implementation for provisioning.
2
3use std::sync::Arc;
4
5use convergio_db::pool::ConnPool;
6use convergio_types::extension::{AppContext, Extension, Health, McpToolDef, Metric, Migration};
7use convergio_types::manifest::{Capability, Manifest, ModuleKind};
8
9use crate::routes::{provision_routes, ProvisionState};
10
11pub struct ProvisioningExtension {
12    pool: ConnPool,
13}
14
15impl ProvisioningExtension {
16    pub fn new(pool: ConnPool) -> Self {
17        Self { pool }
18    }
19}
20
21impl Extension for ProvisioningExtension {
22    fn manifest(&self) -> Manifest {
23        Manifest {
24            id: "convergio-provisioning".to_string(),
25            description: "Node provisioning — sync config, keys, binary to remote peers"
26                .to_string(),
27            version: env!("CARGO_PKG_VERSION").to_string(),
28            kind: ModuleKind::Extension,
29            provides: vec![Capability {
30                name: "node-provisioning".to_string(),
31                version: "1.0.0".to_string(),
32                description: "Sync config/keys/binary to peers via rsync/SSH".to_string(),
33            }],
34            requires: vec![],
35            agent_tools: vec![],
36            required_roles: vec!["orchestrator".into(), "all".into()],
37        }
38    }
39
40    fn routes(&self, _ctx: &AppContext) -> Option<axum::Router> {
41        let state = Arc::new(ProvisionState {
42            pool: self.pool.clone(),
43        });
44        Some(provision_routes(state))
45    }
46
47    fn migrations(&self) -> Vec<Migration> {
48        vec![Migration {
49            version: 1,
50            description: "provisioning tables",
51            up: "CREATE TABLE IF NOT EXISTS provision_runs (\
52                    id INTEGER PRIMARY KEY,\
53                    peer_name TEXT NOT NULL,\
54                    ssh_target TEXT NOT NULL,\
55                    status TEXT DEFAULT 'pending',\
56                    items_total INTEGER DEFAULT 0,\
57                    items_done INTEGER DEFAULT 0,\
58                    started_at TEXT DEFAULT (datetime('now')),\
59                    completed_at TEXT,\
60                    error_message TEXT\
61                );\
62                CREATE INDEX IF NOT EXISTS idx_pr_peer \
63                    ON provision_runs(peer_name);\
64                CREATE INDEX IF NOT EXISTS idx_pr_status \
65                    ON provision_runs(status);\
66                CREATE TABLE IF NOT EXISTS provision_items (\
67                    id INTEGER PRIMARY KEY,\
68                    run_id INTEGER NOT NULL REFERENCES provision_runs(id),\
69                    item_type TEXT NOT NULL,\
70                    source_path TEXT,\
71                    dest_path TEXT,\
72                    status TEXT DEFAULT 'pending',\
73                    bytes_transferred INTEGER DEFAULT 0,\
74                    duration_ms INTEGER DEFAULT 0,\
75                    error_message TEXT\
76                );\
77                CREATE INDEX IF NOT EXISTS idx_pi_run \
78                    ON provision_items(run_id);",
79        }]
80    }
81
82    fn health(&self) -> Health {
83        match self.pool.get() {
84            Ok(_) => Health::Ok,
85            Err(e) => Health::Degraded {
86                reason: format!("db: {e}"),
87            },
88        }
89    }
90
91    fn metrics(&self) -> Vec<Metric> {
92        let run_count: f64 = self
93            .pool
94            .get()
95            .ok()
96            .and_then(|c| {
97                c.query_row("SELECT COUNT(*) FROM provision_runs", [], |r| {
98                    r.get::<_, i64>(0)
99                })
100                .ok()
101            })
102            .unwrap_or(0) as f64;
103        vec![Metric {
104            name: "provision_runs_total".to_string(),
105            value: run_count,
106            labels: vec![],
107        }]
108    }
109
110    fn mcp_tools(&self) -> Vec<McpToolDef> {
111        crate::mcp_defs::provisioning_tools()
112    }
113}