//! SyncKit field-version task. Polls MNW's internal client-versions endpoint on a //! per-target interval and records the distribution so `target_status` can answer //! which SDK is actually in the field. //! //! Unlike every other task here, this one fires no alerts. There is no bad //! distribution to alert on (see `pom::checks::synckit_fleet`), and paging on the //! remaining case, a readout PoM cannot take, would mean an email every hour for //! as long as a stale token went unnoticed. It surfaces as a degraded condition //! on the target instead, which is where an operator already looks. use tokio::task::JoinHandle; use tracing::{info, warn}; use pom::checks::synckit_fleet; use pom::config::Config; use pom::db; use super::{CheckInterval, configured_targets}; pub(crate) fn spawn_synckit_fleet_tasks( config: &Config, pool: &sqlx::SqlitePool, cancel: &tokio_util::sync::CancellationToken, ) -> Vec> { let mut handles = Vec::new(); // The endpoint is authed with the alerts ingest token, so an alert config that // omits it leaves this check with no credential. Read it once here rather than // per tick: a token that appears later arrives by config reload, which restarts // these tasks anyway. let token = config .alerts .as_ref() .and_then(|a| a.alerts_ingest_token.clone()); for (name, target_config) in configured_targets(config) { let Some(fleet_config) = target_config.synckit_fleet else { continue; }; let Some(token) = token.clone() else { // Loud, because the alternative is a configured check that silently // never runs and a fleet column that stays blank for weeks. warn!( "{name}: synckit fleet check configured but alerts.alerts_ingest_token is unset, \ not spawning" ); continue; }; let name = name.clone(); let pool = pool.clone(); let cancel = cancel.clone(); info!( "{name}: synckit fleet check every {}s against {} ({}d window)", fleet_config.interval_secs, fleet_config.base_url, fleet_config.window_days, ); handles.push(tokio::spawn(async move { let mut ticks = CheckInterval::new(fleet_config.interval_secs, cancel); while ticks.next().await { let result = synckit_fleet::check_synckit_fleet( &name, &fleet_config.base_url, &token, fleet_config.window_days, fleet_config.timeout_secs, ) .await; match &result.error { Some(error) => warn!( target = %name, %error, "synckit fleet readout unavailable" ), None => info!( "{name}: synckit fleet {} device(s) over {}d across {} version(s)", result.devices, result.window_days, result.versions.len() ), } if let Err(e) = db::insert_synckit_fleet_check(&pool, &result).await { tracing::error!("{name}: failed to store synckit fleet check: {e}"); } } })); } handles }