athena_rs 3.26.1

Hyper performant polyglot Database driver
Documentation
//! Capability-model and capability-detection helpers for health endpoints.
//!
//! This module owns capability data structures and local capability probing
//! logic, so route handlers and mirror probing can consume a shared contract.

use serde::{Deserialize, Serialize};
use std::path::Path;
use which::which;

/// Availability status for one cluster capability probe.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClusterCapabilityStatus {
    /// Whether the capability is currently available.
    pub available: bool,
    /// Human-readable explanation of the availability result.
    pub detail: String,
}

/// Capability matrix advertised by local or mirrored Athena nodes.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClusterCapabilities {
    /// Backup dump tool availability.
    pub pg_dump: ClusterCapabilityStatus,
    /// Backup restore tool availability.
    pub pg_restore: ClusterCapabilityStatus,
    #[serde(default = "unknown_capability")]
    /// `s3cmd` CLI availability.
    pub s3cmd: ClusterCapabilityStatus,
    /// S3 configuration readiness.
    pub s3_access: ClusterCapabilityStatus,
    /// End-to-end backup-create readiness.
    pub backup_create: ClusterCapabilityStatus,
    /// End-to-end backup-restore readiness.
    pub backup_restore: ClusterCapabilityStatus,
    /// OpenAPI download availability.
    pub openapi_download: ClusterCapabilityStatus,
    /// Management API route availability.
    pub management_api: ClusterCapabilityStatus,
    /// Gateway fetch route availability.
    pub gateway_fetch: ClusterCapabilityStatus,
    #[serde(default)]
    /// Optional compile-time capability flag for experimental deadpool mode.
    pub deadpool_experimental: Option<ClusterCapabilityStatus>,
}

/// Default capability value used when mirrors do not report a capability.
fn unknown_capability() -> ClusterCapabilityStatus {
    ClusterCapabilityStatus {
        available: false,
        detail: "Capability not reported by this node".to_string(),
    }
}

/// Returns whether an environment variable is present and non-empty.
fn env_non_empty(key: &str) -> bool {
    std::env::var(key)
        .ok()
        .map(|value| !value.trim().is_empty())
        .unwrap_or(false)
}

/// Resolves a tool path from env override, PATH lookup, or Windows fallbacks.
fn detect_tool_path(
    env_key: &str,
    fallback_names: &[&str],
    fallback_windows: &[&str],
) -> Option<String> {
    if let Ok(path) = std::env::var(env_key) {
        if !path.trim().is_empty() && Path::new(path.trim()).is_file() {
            return Some(path);
        }
    }

    for name in fallback_names {
        if let Ok(path) = which(name) {
            return Some(path.display().to_string());
        }
    }

    #[cfg(target_os = "windows")]
    {
        for candidate in fallback_windows {
            if Path::new(candidate).is_file() {
                return Some((*candidate).to_string());
            }
        }
    }

    None
}

/// Computes capability status for the local node.
pub(super) fn local_capabilities() -> ClusterCapabilities {
    let dump_path: Option<String> = detect_tool_path(
        "ATHENA_PG_DUMP_PATH",
        &["pg_dump"],
        &[r"C:\Program Files\PostgreSQL\18\bin\pg_dump.exe"],
    );
    let restore_path: Option<String> = detect_tool_path(
        "ATHENA_PG_RESTORE_PATH",
        &["pg_restore"],
        &[r"C:\Program Files\PostgreSQL\18\bin\pg_restore.exe"],
    );
    let has_dump: bool = dump_path.is_some();
    let has_restore: bool = restore_path.is_some();
    let s3cmd_path: Option<String> = detect_tool_path("ATHENA_S3CMD_PATH", &["s3cmd"], &[]);
    let has_s3cmd: bool = s3cmd_path.is_some();

    let has_bucket: bool = env_non_empty("ATHENA_BACKUP_S3_BUCKET");
    let has_access_key: bool = env_non_empty("ATHENA_BACKUP_S3_ACCESS_KEY");
    let has_secret_key: bool = env_non_empty("ATHENA_BACKUP_S3_SECRET_KEY");
    let dump_detail: String = dump_path
        .as_ref()
        .map(|path| format!("resolved at {}", path))
        .unwrap_or_else(|| "pg_dump not found (ATHENA_PG_DUMP_PATH/PATH)".to_string());
    let restore_detail: String = restore_path
        .as_ref()
        .map(|path| format!("resolved at {}", path))
        .unwrap_or_else(|| "pg_restore not found (ATHENA_PG_RESTORE_PATH/PATH)".to_string());
    let s3cmd_detail: String = s3cmd_path
        .as_ref()
        .map(|path| format!("resolved at {}", path))
        .unwrap_or_else(|| "s3cmd not found (ATHENA_S3CMD_PATH/PATH)".to_string());

    let s3_ready: bool =
        has_bucket && ((has_access_key && has_secret_key) || (!has_access_key && !has_secret_key));
    let s3_detail: String = if !has_bucket {
        "ATHENA_BACKUP_S3_BUCKET is not set".to_string()
    } else if has_access_key ^ has_secret_key {
        "Set both ATHENA_BACKUP_S3_ACCESS_KEY and ATHENA_BACKUP_S3_SECRET_KEY, or neither"
            .to_string()
    } else {
        "S3 backup environment variables are configured".to_string()
    };

    ClusterCapabilities {
        pg_dump: ClusterCapabilityStatus {
            available: has_dump,
            detail: dump_detail,
        },
        pg_restore: ClusterCapabilityStatus {
            available: has_restore,
            detail: restore_detail,
        },
        s3cmd: ClusterCapabilityStatus {
            available: has_s3cmd,
            detail: s3cmd_detail,
        },
        s3_access: ClusterCapabilityStatus {
            available: s3_ready,
            detail: s3_detail,
        },
        backup_create: ClusterCapabilityStatus {
            available: has_dump && s3_ready,
            detail: if has_dump && s3_ready {
                "Backup creation dependencies are available".to_string()
            } else {
                "Requires pg_dump and S3 access".to_string()
            },
        },
        backup_restore: ClusterCapabilityStatus {
            available: has_restore && s3_ready,
            detail: if has_restore && s3_ready {
                "Backup restore dependencies are available".to_string()
            } else {
                "Requires pg_restore and S3 access".to_string()
            },
        },
        openapi_download: ClusterCapabilityStatus {
            available: true,
            detail: "OpenAPI route is exposed at /openapi.yaml".to_string(),
        },
        management_api: ClusterCapabilityStatus {
            available: true,
            detail: "Management routes are exposed under /management/*".to_string(),
        },
        gateway_fetch: ClusterCapabilityStatus {
            available: true,
            detail: "Gateway fetch route is exposed at /gateway/fetch".to_string(),
        },
        deadpool_experimental: Some(ClusterCapabilityStatus {
            available: cfg!(feature = "deadpool_experimental"),
            detail: if cfg!(feature = "deadpool_experimental") {
                "Experimental deadpool backend compiled in".to_string()
            } else {
                "Experimental deadpool backend not compiled in".to_string()
            },
        }),
    }
}

/// Builds a capability matrix where all capabilities are unavailable with one reason.
pub(super) fn unavailable_capabilities(reason: &str) -> ClusterCapabilities {
    ClusterCapabilities {
        pg_dump: ClusterCapabilityStatus {
            available: false,
            detail: reason.to_string(),
        },
        pg_restore: ClusterCapabilityStatus {
            available: false,
            detail: reason.to_string(),
        },
        s3cmd: ClusterCapabilityStatus {
            available: false,
            detail: reason.to_string(),
        },
        s3_access: ClusterCapabilityStatus {
            available: false,
            detail: reason.to_string(),
        },
        backup_create: ClusterCapabilityStatus {
            available: false,
            detail: reason.to_string(),
        },
        backup_restore: ClusterCapabilityStatus {
            available: false,
            detail: reason.to_string(),
        },
        openapi_download: ClusterCapabilityStatus {
            available: false,
            detail: reason.to_string(),
        },
        management_api: ClusterCapabilityStatus {
            available: false,
            detail: reason.to_string(),
        },
        gateway_fetch: ClusterCapabilityStatus {
            available: false,
            detail: reason.to_string(),
        },
        deadpool_experimental: None,
    }
}