Skip to main content

ahri_tre_pgmeta/
preflight.rs

1//! Read-only metadata diagnostic over an already selected service authority.
2use crate::{DatastoreSchemaCompatibility, datastore_schema_status};
3use ahri_tre_libpq_oauth::{LibpqOAuthConnector, LibpqServiceProbeFailure};
4pub use ahri_tre_libpq_oauth::{
5    LibpqOAuthTls as MetadataProbeTls, LibpqServiceProbeConfig as MetadataProbeConfig,
6};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum MetadataProbeStage {
10    ConnectionTls,
11    Authentication,
12    Schema,
13    Read,
14}
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum MetadataProbeFailure {
17    Connection,
18    Tls,
19    Authentication,
20    Timeout,
21    Compatibility,
22    Query,
23}
24
25/// Reports each completed stage immediately so a caller can retain partial evidence.
26/// `inspect_schema` is false for a creation bootstrap database: it need not be a
27/// Datastore. Queries never install a schema or read governed rows.
28pub fn probe_metadata(
29    config: &MetadataProbeConfig<'_>,
30    inspect_schema: bool,
31    mut observe: impl FnMut(MetadataProbeStage, Result<(), MetadataProbeFailure>),
32) {
33    use MetadataProbeFailure as Failure;
34    use MetadataProbeStage as Stage;
35    let mut connection = match LibpqOAuthConnector.open_read_only_service(config) {
36        Ok(connection) => connection,
37        Err(error) => {
38            let failure = match error {
39                LibpqServiceProbeFailure::Connection => Failure::Connection,
40                LibpqServiceProbeFailure::Tls => Failure::Tls,
41                LibpqServiceProbeFailure::Authentication => Failure::Authentication,
42                LibpqServiceProbeFailure::Timeout => Failure::Timeout,
43            };
44            // Authentication rejection implies a completed verified TLS exchange.
45            if failure == Failure::Authentication {
46                observe(Stage::ConnectionTls, Ok(()));
47                observe(Stage::Authentication, Err(failure));
48            } else {
49                observe(Stage::ConnectionTls, Err(failure));
50            }
51            return;
52        }
53    };
54    observe(Stage::ConnectionTls, Ok(()));
55    observe(Stage::Authentication, Ok(()));
56    // Independent of schema compatibility: a minimal read can succeed on an
57    // incompatible database and must not be concealed by that incompatibility.
58    observe(
59        Stage::Read,
60        connection
61            .query_one_raw("SELECT 1", &[])
62            .map(|_| ())
63            .map_err(|_| Failure::Query),
64    );
65    if inspect_schema {
66        observe(
67            Stage::Schema,
68            match datastore_schema_status(&mut connection) {
69                Ok(status)
70                    if matches!(
71                        status.status,
72                        DatastoreSchemaCompatibility::Current
73                            | DatastoreSchemaCompatibility::Pending
74                    ) =>
75                {
76                    Ok(())
77                }
78                Ok(_) => Err(Failure::Compatibility),
79                Err(_) => Err(Failure::Query),
80            },
81        );
82    }
83}