1
//! Brings every tenant database to the current schema.
2
//!
3
//! Runs as its own process — a k8s Job, not a serving container. See
4
//! [`server::fleet`] for why this must never live inside `boot()`.
5
//!
6
//! Exit codes are the Job's contract: `0` means every tenant was verified at the
7
//! current schema, `1` means the run left something undone — a tenant
8
//! quarantined, one still held by another worker, or a verdict that could not be
9
//! written — and `2` means the run could not be performed at all. Only `0` may
10
//! gate a rollout: the other two all mean some customer's database is in a state
11
//! nobody has confirmed.
12

            
13
use server::bootstrap::ADMIN_USER_ID;
14
use server::db::DBError;
15
use server::fleet::migrate_fleet;
16
use sqlx::types::Uuid;
17
use std::process::ExitCode;
18

            
19
#[cfg(test)]
20
mod tests;
21

            
22
/// Every tenant verified at the current schema.
23
const HEALTHY: u8 = 0;
24
/// The run finished but left something unconfirmed.
25
const DEGRADED: u8 = 1;
26
/// The run could not be performed.
27
const UNUSABLE: u8 = 2;
28

            
29
/// The Job's contract with Flux, kept separate from the logging so it can be
30
/// tested: a wrong code here is the difference between a rollout that stops and
31
/// one that proceeds over a database nobody has confirmed.
32
6
fn verdict(outcome: &Result<server::fleet::FleetReport, DBError>) -> u8 {
33
5
    match outcome {
34
5
        Ok(report) if report.is_healthy() => HEALTHY,
35
3
        Ok(_) => DEGRADED,
36
1
        Err(_) => UNUSABLE,
37
    }
38
6
}
39

            
40
5
fn report_outcome(outcome: &Result<server::fleet::FleetReport, DBError>) {
41
4
    match outcome {
42
4
        Ok(report) if report.is_healthy() => {
43
1
            log::info!("fleet is at the current schema: {}", report.summary());
44
        }
45
3
        Ok(report) => {
46
3
            log::error!("fleet migration degraded: {}", report.summary());
47
3
            for (id, reason) in &report.quarantined {
48
1
                log::error!("  {id} quarantined: {reason}");
49
            }
50
3
            for id in &report.unresolved {
51
1
                log::error!("  {id} was held by another worker and never verified here");
52
            }
53
3
            for (id, reason) in &report.unpublished {
54
1
                log::error!("  {id} has a verdict that could not be recorded: {reason}");
55
            }
56
        }
57
1
        Err(e) => log::error!("fleet migration could not run: {e}"),
58
    }
59
5
}
60

            
61
#[tokio::main]
62
async fn main() -> ExitCode {
63
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
64

            
65
    let outcome = run().await;
66
    report_outcome(&outcome);
67
    ExitCode::from(verdict(&outcome))
68
}
69

            
70
async fn run() -> Result<server::fleet::FleetReport, DBError> {
71
    sqlx::any::install_default_drivers();
72

            
73
    let admin = ADMIN_USER_ID
74
        .parse::<Uuid>()
75
        .map_err(|e| DBError::FleetWorkerLost(format!("admin id is not a uuid: {e}")))?;
76

            
77
    migrate_fleet(admin).await
78
}