Skip to main content

ares_store/
plugins.rs

1//! Loader factories for `ares-store`.
2//!
3//! With `postgres`, `register_plugins` installs the `Store` factory. Without
4//! that feature the function is a no-op so other crates can always call it.
5
6#[cfg(feature = "postgres")]
7use std::sync::Arc;
8
9#[cfg(feature = "postgres")]
10use cordis::{CordisError, FiberId};
11
12#[cfg(feature = "postgres")]
13fn block_on_async<F: std::future::Future>(fut: F) -> F::Output {
14    tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(fut))
15}
16
17#[cfg(feature = "postgres")]
18fn block_on_plugin<S: cordis::Service + 'static>(
19    ctx: &Arc<cordis::Context>,
20    svc: S,
21) -> Result<FiberId, CordisError> {
22    tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(ctx.plugin(svc)))
23}
24
25/// Register this crate's loader factories on `reg` (manual fallback path).
26pub fn register_plugins(reg: &cordis::PluginRegistry) {
27    #[cfg(feature = "postgres")]
28    {
29        reg.register("Store", Arc::new(factory_store));
30    }
31    #[cfg(not(feature = "postgres"))]
32    {
33        let _ = reg;
34    }
35}
36
37#[cfg(all(feature = "inventory", feature = "postgres"))]
38inventory::submit! {
39    cordis::CordisPluginFactory { name: "Store", make: factory_store }
40}
41
42#[cfg(feature = "postgres")]
43fn factory_store(
44    ctx: &Arc<cordis::Context>,
45    config: &serde_json::Value,
46) -> Result<FiberId, CordisError> {
47    let db: crate::DatabaseConfig =
48        if config.is_null() || config.as_object().is_some_and(|obj| obj.is_empty()) {
49            crate::DatabaseConfig::default()
50        } else {
51            serde_json::from_value(config.clone())
52                .map_err(|e| CordisError::Configuration(e.to_string()))?
53        };
54
55    let url = crate::postgres::resolve_database_url(Some(&db.url));
56    let pg = block_on_async(crate::PostgresClient::new_remote(url, String::new()))
57        .map_err(|e| CordisError::Configuration(e.to_string()))?;
58    let pg_arc = Arc::new(pg);
59    ctx.provide_arc(pg_arc.clone());
60
61    let tenant = crate::TenantDb::new(pg_arc.clone());
62    let fid = block_on_plugin(ctx, tenant)?;
63
64    let pg = ctx
65        .get::<crate::TenantDb>()
66        .ok_or_else(|| CordisError::Configuration("TenantDb missing after Store factory".into()))?;
67    block_on_async(sqlx::migrate!("../../migrations").run(pg.pool())).map_err(|e| {
68        CordisError::Configuration(format!("Failed to run database migrations: {e}"))
69    })?;
70    tracing::info!("Database migrations applied");
71    block_on_async(crate::tenant_agents::seed_default_templates(pg.pool()))
72        .map_err(|e| CordisError::Configuration(format!("Failed to seed agent templates: {e}")))?;
73    tracing::info!("Agent templates seeded");
74
75    let fleet_secrets = crate::FleetSecrets::new();
76    let fleet_store = crate::fleet_provider_secrets::FleetProviderSecretsStore::new(&pg_arc.pool);
77    let master = crate::MasterKey::from_env();
78    match block_on_async(fleet_store.load_all(master.as_ref())) {
79        Ok(providers) => fleet_secrets.store(providers),
80        Err(e) => {
81            tracing::warn!(error = %e, "Failed to load fleet provider secrets");
82        }
83    }
84    block_on_plugin(ctx, fleet_secrets)?;
85    Ok(fid)
86}
87
88#[cfg(test)]
89mod tests {
90    use super::register_plugins;
91    use cordis::PluginRegistry;
92
93    #[test]
94    fn register_plugins_store_key() {
95        let reg = PluginRegistry::new();
96        register_plugins(&reg);
97        #[cfg(feature = "postgres")]
98        assert!(reg.get("Store").is_some());
99        #[cfg(not(feature = "postgres"))]
100        assert!(reg.get("Store").is_none());
101    }
102}