use crate::Error;
use crate::constants;
use std::env;
use std::path::PathBuf;
pub const MODEL_EXPRESS_PREFIX: &str = "MODEL_EXPRESS";
pub const MODEL_EXPRESS_ENDPOINT: &str = "MODEL_EXPRESS_ENDPOINT";
pub const MODEL_EXPRESS_TIMEOUT: &str = "MODEL_EXPRESS_TIMEOUT";
pub const MODEL_EXPRESS_CACHE_DIRECTORY: &str = "MODEL_EXPRESS_CACHE_DIRECTORY";
pub const MODEL_EXPRESS_LOG_LEVEL: &str = "MODEL_EXPRESS_LOG_LEVEL";
pub const MODEL_EXPRESS_LOG_FORMAT: &str = "MODEL_EXPRESS_LOG_FORMAT";
pub const MODEL_EXPRESS_MAX_RETRIES: &str = "MODEL_EXPRESS_MAX_RETRIES";
pub const MODEL_EXPRESS_RETRY_DELAY: &str = "MODEL_EXPRESS_RETRY_DELAY";
pub const MODEL_EXPRESS_NO_SHARED_STORAGE: &str = "MODEL_EXPRESS_NO_SHARED_STORAGE";
pub const MODEL_EXPRESS_TRANSFER_CHUNK_SIZE: &str = "MODEL_EXPRESS_TRANSFER_CHUNK_SIZE";
pub const MODEL_EXPRESS_SERVER_PORT: &str = "MODEL_EXPRESS_SERVER_PORT";
pub const MODEL_EXPRESS_SERVER_HOST: &str = "MODEL_EXPRESS_SERVER_HOST";
pub const MODEL_EXPRESS_CACHE_EVICTION_ENABLED: &str = "MODEL_EXPRESS_CACHE_EVICTION_ENABLED";
pub const MODEL_EXPRESS_SERVER_ENDPOINT: &str = "MODEL_EXPRESS_SERVER_ENDPOINT";
pub const HF_TOKEN: &str = "HF_TOKEN";
pub const HF_HUB_CACHE: &str = "HF_HUB_CACHE";
pub const HF_HUB_OFFLINE: &str = "HF_HUB_OFFLINE";
pub const HF_ENDPOINT: &str = "HF_ENDPOINT";
pub const NGC_API_ENDPOINT: &str = "NGC_API_ENDPOINT";
pub const NGC_AUTH_ENDPOINT: &str = "NGC_AUTH_ENDPOINT";
pub const NGC_API_KEY: &str = "NGC_API_KEY";
pub const NGC_CLI_API_KEY: &str = "NGC_CLI_API_KEY";
pub const NGC_CLI_HOME: &str = "NGC_CLI_HOME";
pub const DEFAULT_NGC_API_BASE: &str = "https://api.ngc.nvidia.com";
pub const DEFAULT_NGC_AUTHN_BASE: &str = "https://authn.nvidia.com";
pub const MX_METADATA_BACKEND: &str = "MX_METADATA_BACKEND";
pub const REDIS_URL: &str = "REDIS_URL";
pub const MX_REDIS_HOST: &str = "MX_REDIS_HOST";
pub const REDIS_HOST: &str = "REDIS_HOST";
pub const MX_REDIS_PORT: &str = "MX_REDIS_PORT";
pub const REDIS_PORT: &str = "REDIS_PORT";
pub const MX_METADATA_NAMESPACE: &str = "MX_METADATA_NAMESPACE";
pub const POD_NAMESPACE: &str = "POD_NAMESPACE";
pub const POD_NAME: &str = "POD_NAME";
pub const POD_UID: &str = "POD_UID";
pub const MX_REAPER_SCAN_INTERVAL_SECS: &str = "MX_REAPER_SCAN_INTERVAL_SECS";
pub const MX_HEARTBEAT_TIMEOUT_SECS: &str = "MX_HEARTBEAT_TIMEOUT_SECS";
pub const MX_GC_TIMEOUT_SECS: &str = "MX_GC_TIMEOUT_SECS";
pub const HOME: &str = "HOME";
pub const USERPROFILE: &str = "USERPROFILE";
pub const KUBECONFIG: &str = "KUBECONFIG";
const DEFAULT_REAPER_SCAN_INTERVAL_SECS: u64 = 30;
const DEFAULT_HEARTBEAT_TIMEOUT_SECS: u64 = 90;
const DEFAULT_GC_TIMEOUT_SECS: u64 = 3600;
pub fn home_dir() -> std::result::Result<String, Box<Error>> {
env::var(HOME)
.or_else(|_| env::var(USERPROFILE))
.map_err(|e| Error::Generic(format!("Failed to get home directory: {e}")).into())
}
pub fn home_dir_or_cwd() -> PathBuf {
PathBuf::from(home_dir().unwrap_or_else(|_| ".".to_string()))
}
pub fn cache_directory() -> Option<PathBuf> {
env::var(MODEL_EXPRESS_CACHE_DIRECTORY)
.ok()
.map(PathBuf::from)
}
pub fn server_endpoint_or_default() -> String {
env::var(MODEL_EXPRESS_SERVER_ENDPOINT)
.unwrap_or_else(|_| format!("http://localhost:{}", constants::DEFAULT_GRPC_PORT))
}
pub fn hf_token() -> Option<String> {
env::var(HF_TOKEN).ok()
}
pub fn hf_hub_cache() -> Option<PathBuf> {
env::var(HF_HUB_CACHE).ok().map(PathBuf::from)
}
pub fn hf_offline() -> bool {
env::var(HF_HUB_OFFLINE)
.map(|v| matches!(v.to_uppercase().as_str(), "1" | "ON" | "YES" | "TRUE"))
.unwrap_or(false)
}
pub fn ngc_api_base() -> String {
env::var(NGC_API_ENDPOINT).unwrap_or_else(|_| DEFAULT_NGC_API_BASE.to_string())
}
pub fn ngc_authn_base() -> String {
env::var(NGC_AUTH_ENDPOINT).unwrap_or_else(|_| DEFAULT_NGC_AUTHN_BASE.to_string())
}
pub fn ngc_api_key() -> Option<String> {
for var in [NGC_API_KEY, NGC_CLI_API_KEY] {
if let Ok(v) = env::var(var) {
let trimmed = v.trim().to_string();
if !trimmed.is_empty() {
return Some(trimmed);
}
}
}
None
}
pub fn ngc_cli_home() -> Option<PathBuf> {
env::var(NGC_CLI_HOME).ok().map(PathBuf::from)
}
pub fn metadata_backend() -> String {
env::var(MX_METADATA_BACKEND).unwrap_or_default()
}
pub fn redis_url() -> Option<String> {
env::var(REDIS_URL).ok()
}
pub fn redis_host() -> Option<String> {
env::var(MX_REDIS_HOST)
.or_else(|_| env::var(REDIS_HOST))
.ok()
}
pub fn redis_port() -> Option<String> {
env::var(MX_REDIS_PORT)
.or_else(|_| env::var(REDIS_PORT))
.ok()
}
pub fn metadata_namespace() -> Option<String> {
env::var(MX_METADATA_NAMESPACE)
.or_else(|_| env::var(POD_NAMESPACE))
.ok()
}
pub fn reaper_scan_interval_secs() -> u64 {
env_u64(
MX_REAPER_SCAN_INTERVAL_SECS,
DEFAULT_REAPER_SCAN_INTERVAL_SECS,
)
}
pub fn heartbeat_timeout_secs() -> u64 {
env_u64(MX_HEARTBEAT_TIMEOUT_SECS, DEFAULT_HEARTBEAT_TIMEOUT_SECS)
}
pub fn gc_timeout_secs() -> u64 {
env_u64(MX_GC_TIMEOUT_SECS, DEFAULT_GC_TIMEOUT_SECS)
}
fn env_u64(name: &str, default: u64) -> u64 {
env::var(name)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
use crate::test_support::{EnvVarGuard, acquire_env_mutex};
#[test]
fn name_constants_match_their_literals() {
assert_eq!(MODEL_EXPRESS_PREFIX, "MODEL_EXPRESS");
assert_eq!(MODEL_EXPRESS_ENDPOINT, "MODEL_EXPRESS_ENDPOINT");
assert_eq!(MODEL_EXPRESS_TIMEOUT, "MODEL_EXPRESS_TIMEOUT");
assert_eq!(
MODEL_EXPRESS_CACHE_DIRECTORY,
"MODEL_EXPRESS_CACHE_DIRECTORY"
);
assert_eq!(MODEL_EXPRESS_LOG_LEVEL, "MODEL_EXPRESS_LOG_LEVEL");
assert_eq!(MODEL_EXPRESS_LOG_FORMAT, "MODEL_EXPRESS_LOG_FORMAT");
assert_eq!(MODEL_EXPRESS_MAX_RETRIES, "MODEL_EXPRESS_MAX_RETRIES");
assert_eq!(MODEL_EXPRESS_RETRY_DELAY, "MODEL_EXPRESS_RETRY_DELAY");
assert_eq!(
MODEL_EXPRESS_NO_SHARED_STORAGE,
"MODEL_EXPRESS_NO_SHARED_STORAGE"
);
assert_eq!(
MODEL_EXPRESS_TRANSFER_CHUNK_SIZE,
"MODEL_EXPRESS_TRANSFER_CHUNK_SIZE"
);
assert_eq!(MODEL_EXPRESS_SERVER_PORT, "MODEL_EXPRESS_SERVER_PORT");
assert_eq!(MODEL_EXPRESS_SERVER_HOST, "MODEL_EXPRESS_SERVER_HOST");
assert_eq!(
MODEL_EXPRESS_CACHE_EVICTION_ENABLED,
"MODEL_EXPRESS_CACHE_EVICTION_ENABLED"
);
assert_eq!(
MODEL_EXPRESS_SERVER_ENDPOINT,
"MODEL_EXPRESS_SERVER_ENDPOINT"
);
assert_eq!(HF_TOKEN, "HF_TOKEN");
assert_eq!(HF_HUB_CACHE, "HF_HUB_CACHE");
assert_eq!(HF_HUB_OFFLINE, "HF_HUB_OFFLINE");
assert_eq!(HF_ENDPOINT, "HF_ENDPOINT");
assert_eq!(NGC_API_ENDPOINT, "NGC_API_ENDPOINT");
assert_eq!(NGC_AUTH_ENDPOINT, "NGC_AUTH_ENDPOINT");
assert_eq!(NGC_API_KEY, "NGC_API_KEY");
assert_eq!(NGC_CLI_API_KEY, "NGC_CLI_API_KEY");
assert_eq!(NGC_CLI_HOME, "NGC_CLI_HOME");
assert_eq!(MX_METADATA_BACKEND, "MX_METADATA_BACKEND");
assert_eq!(REDIS_URL, "REDIS_URL");
assert_eq!(MX_REDIS_HOST, "MX_REDIS_HOST");
assert_eq!(REDIS_HOST, "REDIS_HOST");
assert_eq!(MX_REDIS_PORT, "MX_REDIS_PORT");
assert_eq!(REDIS_PORT, "REDIS_PORT");
assert_eq!(MX_METADATA_NAMESPACE, "MX_METADATA_NAMESPACE");
assert_eq!(POD_NAMESPACE, "POD_NAMESPACE");
assert_eq!(MX_REAPER_SCAN_INTERVAL_SECS, "MX_REAPER_SCAN_INTERVAL_SECS");
assert_eq!(MX_HEARTBEAT_TIMEOUT_SECS, "MX_HEARTBEAT_TIMEOUT_SECS");
assert_eq!(MX_GC_TIMEOUT_SECS, "MX_GC_TIMEOUT_SECS");
assert_eq!(HOME, "HOME");
assert_eq!(USERPROFILE, "USERPROFILE");
assert_eq!(KUBECONFIG, "KUBECONFIG");
assert_eq!(POD_NAME, "POD_NAME");
assert_eq!(POD_UID, "POD_UID");
}
#[test]
fn hf_offline_parses_truthy_values() {
let lock = acquire_env_mutex();
for truthy in ["1", "on", "YES", "true", "True"] {
let _g = EnvVarGuard::set(&lock, HF_HUB_OFFLINE, truthy);
assert!(hf_offline(), "expected {truthy} to enable offline mode");
}
for falsey in ["0", "off", "no", "maybe"] {
let _g = EnvVarGuard::set(&lock, HF_HUB_OFFLINE, falsey);
assert!(!hf_offline(), "expected {falsey} to disable offline mode");
}
let _g = EnvVarGuard::remove(&lock, HF_HUB_OFFLINE);
assert!(!hf_offline(), "unset should disable offline mode");
}
#[test]
fn ngc_bases_default_then_override() {
let lock = acquire_env_mutex();
let _api = EnvVarGuard::remove(&lock, NGC_API_ENDPOINT);
let _authn = EnvVarGuard::remove(&lock, NGC_AUTH_ENDPOINT);
assert_eq!(ngc_api_base(), DEFAULT_NGC_API_BASE);
assert_eq!(ngc_authn_base(), DEFAULT_NGC_AUTHN_BASE);
let _api = EnvVarGuard::set(&lock, NGC_API_ENDPOINT, "https://api.example.com");
let _authn = EnvVarGuard::set(&lock, NGC_AUTH_ENDPOINT, "https://authn.example.com");
assert_eq!(ngc_api_base(), "https://api.example.com");
assert_eq!(ngc_authn_base(), "https://authn.example.com");
}
#[test]
fn ngc_api_key_prefers_primary_then_falls_back() {
let lock = acquire_env_mutex();
let _p = EnvVarGuard::set(&lock, NGC_API_KEY, " primary ");
let _s = EnvVarGuard::set(&lock, NGC_CLI_API_KEY, "secondary");
assert_eq!(ngc_api_key().as_deref(), Some("primary"));
let _p = EnvVarGuard::remove(&lock, NGC_API_KEY);
assert_eq!(ngc_api_key().as_deref(), Some("secondary"));
let _s = EnvVarGuard::remove(&lock, NGC_CLI_API_KEY);
assert_eq!(ngc_api_key(), None);
}
#[test]
fn redis_and_namespace_fallbacks() {
let lock = acquire_env_mutex();
let _h1 = EnvVarGuard::remove(&lock, MX_REDIS_HOST);
let _h2 = EnvVarGuard::set(&lock, REDIS_HOST, "legacy-host");
assert_eq!(redis_host().as_deref(), Some("legacy-host"));
let _h1 = EnvVarGuard::set(&lock, MX_REDIS_HOST, "mx-host");
assert_eq!(redis_host().as_deref(), Some("mx-host"));
let _n1 = EnvVarGuard::remove(&lock, MX_METADATA_NAMESPACE);
let _n2 = EnvVarGuard::set(&lock, POD_NAMESPACE, "pod-ns");
assert_eq!(metadata_namespace().as_deref(), Some("pod-ns"));
}
#[test]
fn reaper_getters_default_parse_and_fallback() {
let lock = acquire_env_mutex();
let _g = EnvVarGuard::remove(&lock, MX_REAPER_SCAN_INTERVAL_SECS);
assert_eq!(
reaper_scan_interval_secs(),
DEFAULT_REAPER_SCAN_INTERVAL_SECS
);
let _g = EnvVarGuard::set(&lock, MX_HEARTBEAT_TIMEOUT_SECS, "120");
assert_eq!(heartbeat_timeout_secs(), 120);
let _g = EnvVarGuard::set(&lock, MX_GC_TIMEOUT_SECS, "not-a-number");
assert_eq!(gc_timeout_secs(), DEFAULT_GC_TIMEOUT_SECS);
}
}