1use crate::Error;
27use crate::constants;
28use std::env;
29use std::path::PathBuf;
30
31pub const MODEL_EXPRESS_PREFIX: &str = "MODEL_EXPRESS";
36
37pub const MODEL_EXPRESS_ENDPOINT: &str = "MODEL_EXPRESS_ENDPOINT";
40pub const MODEL_EXPRESS_TIMEOUT: &str = "MODEL_EXPRESS_TIMEOUT";
42pub const MODEL_EXPRESS_CACHE_DIRECTORY: &str = "MODEL_EXPRESS_CACHE_DIRECTORY";
44pub const MODEL_EXPRESS_LOG_LEVEL: &str = "MODEL_EXPRESS_LOG_LEVEL";
46pub const MODEL_EXPRESS_LOG_FORMAT: &str = "MODEL_EXPRESS_LOG_FORMAT";
48pub const MODEL_EXPRESS_NO_SHARED_STORAGE: &str = "MODEL_EXPRESS_NO_SHARED_STORAGE";
50pub const MODEL_EXPRESS_TRANSFER_CHUNK_SIZE: &str = "MODEL_EXPRESS_TRANSFER_CHUNK_SIZE";
52pub const MODEL_EXPRESS_SERVER_PORT: &str = "MODEL_EXPRESS_SERVER_PORT";
54pub const MODEL_EXPRESS_SERVER_HOST: &str = "MODEL_EXPRESS_SERVER_HOST";
56pub const MODEL_EXPRESS_SERVER_METRICS_PORT: &str = "MODEL_EXPRESS_SERVER_METRICS_PORT";
65pub const MODEL_EXPRESS_CACHE_EVICTION_ENABLED: &str = "MODEL_EXPRESS_CACHE_EVICTION_ENABLED";
67pub const MODEL_EXPRESS_SERVER_ENDPOINT: &str = "MODEL_EXPRESS_SERVER_ENDPOINT";
69
70pub const MX_METRICS_SCHEME: &str = "MX_METRICS_SCHEME";
80
81pub const HF_TOKEN: &str = "HF_TOKEN";
84pub const HF_HUB_CACHE: &str = "HF_HUB_CACHE";
86pub const HF_HUB_OFFLINE: &str = "HF_HUB_OFFLINE";
88pub const HF_ENDPOINT: &str = "HF_ENDPOINT";
91
92pub const NGC_API_ENDPOINT: &str = "NGC_API_ENDPOINT";
95pub const NGC_AUTH_ENDPOINT: &str = "NGC_AUTH_ENDPOINT";
97pub const NGC_API_KEY: &str = "NGC_API_KEY";
99pub const NGC_CLI_API_KEY: &str = "NGC_CLI_API_KEY";
101pub const NGC_CLI_HOME: &str = "NGC_CLI_HOME";
103
104pub const DEFAULT_NGC_API_BASE: &str = "https://api.ngc.nvidia.com";
106pub const DEFAULT_NGC_AUTHN_BASE: &str = "https://authn.nvidia.com";
108
109pub const MX_METADATA_BACKEND: &str = "MX_METADATA_BACKEND";
112pub const REDIS_URL: &str = "REDIS_URL";
114pub const MX_REDIS_HOST: &str = "MX_REDIS_HOST";
116pub const REDIS_HOST: &str = "REDIS_HOST";
118pub const MX_REDIS_PORT: &str = "MX_REDIS_PORT";
120pub const REDIS_PORT: &str = "REDIS_PORT";
122pub const MX_METADATA_NAMESPACE: &str = "MX_METADATA_NAMESPACE";
124pub const POD_NAMESPACE: &str = "POD_NAMESPACE";
126
127pub const MX_REAPER_SCAN_INTERVAL_SECS: &str = "MX_REAPER_SCAN_INTERVAL_SECS";
130pub const MX_REGISTRY_STATS_INTERVAL_SECS: &str = "MX_REGISTRY_STATS_INTERVAL_SECS";
132pub const MX_HEARTBEAT_TIMEOUT_SECS: &str = "MX_HEARTBEAT_TIMEOUT_SECS";
134pub const MX_GC_TIMEOUT_SECS: &str = "MX_GC_TIMEOUT_SECS";
136
137pub const MODEL_EXPRESS_SECURITY_MODE: &str = "MODEL_EXPRESS_SECURITY_MODE";
140pub const MODEL_EXPRESS_SECURITY_TOKEN_AUDIENCES: &str = "MODEL_EXPRESS_SECURITY_TOKEN_AUDIENCES";
142pub const MODEL_EXPRESS_SECURITY_ALLOWED_SERVICE_ACCOUNTS: &str =
144 "MODEL_EXPRESS_SECURITY_ALLOWED_SERVICE_ACCOUNTS";
145pub const MODEL_EXPRESS_SECURITY_CACHE_TTL_SECS: &str = "MODEL_EXPRESS_SECURITY_CACHE_TTL_SECS";
147
148pub const MX_AUTH_TOKEN_PATH: &str = "MX_AUTH_TOKEN_PATH";
151pub const MX_AUTH_TOKEN_TTL_SECONDS: &str = "MX_AUTH_TOKEN_TTL_SECONDS";
153
154pub const HOME: &str = "HOME";
157pub const USERPROFILE: &str = "USERPROFILE";
159pub const KUBECONFIG: &str = "KUBECONFIG";
161
162const DEFAULT_REAPER_SCAN_INTERVAL_SECS: u64 = 30;
164const DEFAULT_REGISTRY_STATS_INTERVAL_SECS: u64 = 60;
168const DEFAULT_HEARTBEAT_TIMEOUT_SECS: u64 = 90;
169const DEFAULT_GC_TIMEOUT_SECS: u64 = 3600;
170
171pub fn home_dir() -> std::result::Result<String, Box<Error>> {
178 env::var(HOME)
179 .or_else(|_| env::var(USERPROFILE))
180 .map_err(|e| Error::Generic(format!("Failed to get home directory: {e}")).into())
181}
182
183pub fn home_dir_or_cwd() -> PathBuf {
185 PathBuf::from(home_dir().unwrap_or_else(|_| ".".to_string()))
186}
187
188pub fn cache_directory() -> Option<PathBuf> {
190 env::var(MODEL_EXPRESS_CACHE_DIRECTORY)
191 .ok()
192 .map(PathBuf::from)
193}
194
195pub fn server_endpoint_or_default() -> String {
198 env::var(MODEL_EXPRESS_SERVER_ENDPOINT)
199 .unwrap_or_else(|_| format!("http://localhost:{}", constants::DEFAULT_GRPC_PORT))
200}
201
202pub fn metrics_scheme() -> String {
207 env::var(MX_METRICS_SCHEME).unwrap_or_default()
208}
209
210pub fn hf_token() -> Option<String> {
212 env::var(HF_TOKEN).ok()
213}
214
215pub fn hf_hub_cache() -> Option<PathBuf> {
217 env::var(HF_HUB_CACHE).ok().map(PathBuf::from)
218}
219
220pub fn hf_offline() -> bool {
223 env::var(HF_HUB_OFFLINE)
224 .map(|v| matches!(v.to_uppercase().as_str(), "1" | "ON" | "YES" | "TRUE"))
225 .unwrap_or(false)
226}
227
228pub fn ngc_api_base() -> String {
230 env::var(NGC_API_ENDPOINT).unwrap_or_else(|_| DEFAULT_NGC_API_BASE.to_string())
231}
232
233pub fn ngc_authn_base() -> String {
235 env::var(NGC_AUTH_ENDPOINT).unwrap_or_else(|_| DEFAULT_NGC_AUTHN_BASE.to_string())
236}
237
238pub fn ngc_api_key() -> Option<String> {
241 for var in [NGC_API_KEY, NGC_CLI_API_KEY] {
242 if let Ok(v) = env::var(var) {
243 let trimmed = v.trim().to_string();
244 if !trimmed.is_empty() {
245 return Some(trimmed);
246 }
247 }
248 }
249 None
250}
251
252pub fn ngc_cli_home() -> Option<PathBuf> {
254 env::var(NGC_CLI_HOME).ok().map(PathBuf::from)
255}
256
257pub fn metadata_backend() -> String {
259 env::var(MX_METADATA_BACKEND).unwrap_or_default()
260}
261
262pub fn redis_url() -> Option<String> {
264 env::var(REDIS_URL).ok()
265}
266
267pub fn redis_host() -> Option<String> {
269 env::var(MX_REDIS_HOST)
270 .or_else(|_| env::var(REDIS_HOST))
271 .ok()
272}
273
274pub fn redis_port() -> Option<String> {
276 env::var(MX_REDIS_PORT)
277 .or_else(|_| env::var(REDIS_PORT))
278 .ok()
279}
280
281pub fn metadata_namespace() -> Option<String> {
283 env::var(MX_METADATA_NAMESPACE)
284 .or_else(|_| env::var(POD_NAMESPACE))
285 .ok()
286}
287
288pub fn reaper_scan_interval_secs() -> u64 {
290 env_u64(
291 MX_REAPER_SCAN_INTERVAL_SECS,
292 DEFAULT_REAPER_SCAN_INTERVAL_SECS,
293 )
294}
295
296pub fn registry_stats_interval_secs() -> u64 {
302 env_u64(
303 MX_REGISTRY_STATS_INTERVAL_SECS,
304 DEFAULT_REGISTRY_STATS_INTERVAL_SECS,
305 )
306 .max(1)
307}
308
309pub fn heartbeat_timeout_secs() -> u64 {
311 env_u64(MX_HEARTBEAT_TIMEOUT_SECS, DEFAULT_HEARTBEAT_TIMEOUT_SECS)
312}
313
314pub fn gc_timeout_secs() -> u64 {
316 env_u64(MX_GC_TIMEOUT_SECS, DEFAULT_GC_TIMEOUT_SECS)
317}
318
319fn env_u64(name: &str, default: u64) -> u64 {
321 env::var(name)
322 .ok()
323 .and_then(|v| v.parse().ok())
324 .unwrap_or(default)
325}
326
327#[cfg(test)]
328#[allow(clippy::expect_used)]
329mod tests {
330 use super::*;
331 use crate::test_support::{EnvVarGuard, acquire_env_mutex};
332
333 #[test]
334 fn name_constants_match_their_literals() {
335 assert_eq!(MODEL_EXPRESS_PREFIX, "MODEL_EXPRESS");
336 assert_eq!(MODEL_EXPRESS_ENDPOINT, "MODEL_EXPRESS_ENDPOINT");
337 assert_eq!(MODEL_EXPRESS_TIMEOUT, "MODEL_EXPRESS_TIMEOUT");
338 assert_eq!(
339 MODEL_EXPRESS_CACHE_DIRECTORY,
340 "MODEL_EXPRESS_CACHE_DIRECTORY"
341 );
342 assert_eq!(MODEL_EXPRESS_LOG_LEVEL, "MODEL_EXPRESS_LOG_LEVEL");
343 assert_eq!(MODEL_EXPRESS_LOG_FORMAT, "MODEL_EXPRESS_LOG_FORMAT");
344 assert_eq!(
345 MODEL_EXPRESS_NO_SHARED_STORAGE,
346 "MODEL_EXPRESS_NO_SHARED_STORAGE"
347 );
348 assert_eq!(
349 MODEL_EXPRESS_TRANSFER_CHUNK_SIZE,
350 "MODEL_EXPRESS_TRANSFER_CHUNK_SIZE"
351 );
352 assert_eq!(MODEL_EXPRESS_SERVER_PORT, "MODEL_EXPRESS_SERVER_PORT");
353 assert_eq!(MODEL_EXPRESS_SERVER_HOST, "MODEL_EXPRESS_SERVER_HOST");
354 assert_eq!(
355 MODEL_EXPRESS_SERVER_METRICS_PORT,
356 "MODEL_EXPRESS_SERVER_METRICS_PORT"
357 );
358 assert_eq!(
359 MODEL_EXPRESS_CACHE_EVICTION_ENABLED,
360 "MODEL_EXPRESS_CACHE_EVICTION_ENABLED"
361 );
362 assert_eq!(
363 MODEL_EXPRESS_SERVER_ENDPOINT,
364 "MODEL_EXPRESS_SERVER_ENDPOINT"
365 );
366 assert_eq!(MX_METRICS_SCHEME, "MX_METRICS_SCHEME");
367 assert_eq!(HF_TOKEN, "HF_TOKEN");
368 assert_eq!(HF_HUB_CACHE, "HF_HUB_CACHE");
369 assert_eq!(HF_HUB_OFFLINE, "HF_HUB_OFFLINE");
370 assert_eq!(HF_ENDPOINT, "HF_ENDPOINT");
371 assert_eq!(NGC_API_ENDPOINT, "NGC_API_ENDPOINT");
372 assert_eq!(NGC_AUTH_ENDPOINT, "NGC_AUTH_ENDPOINT");
373 assert_eq!(NGC_API_KEY, "NGC_API_KEY");
374 assert_eq!(NGC_CLI_API_KEY, "NGC_CLI_API_KEY");
375 assert_eq!(NGC_CLI_HOME, "NGC_CLI_HOME");
376 assert_eq!(MX_METADATA_BACKEND, "MX_METADATA_BACKEND");
377 assert_eq!(REDIS_URL, "REDIS_URL");
378 assert_eq!(MX_REDIS_HOST, "MX_REDIS_HOST");
379 assert_eq!(REDIS_HOST, "REDIS_HOST");
380 assert_eq!(MX_REDIS_PORT, "MX_REDIS_PORT");
381 assert_eq!(REDIS_PORT, "REDIS_PORT");
382 assert_eq!(MX_METADATA_NAMESPACE, "MX_METADATA_NAMESPACE");
383 assert_eq!(POD_NAMESPACE, "POD_NAMESPACE");
384 assert_eq!(MX_REAPER_SCAN_INTERVAL_SECS, "MX_REAPER_SCAN_INTERVAL_SECS");
385 assert_eq!(
386 MX_REGISTRY_STATS_INTERVAL_SECS,
387 "MX_REGISTRY_STATS_INTERVAL_SECS"
388 );
389 assert_eq!(MX_HEARTBEAT_TIMEOUT_SECS, "MX_HEARTBEAT_TIMEOUT_SECS");
390 assert_eq!(MX_GC_TIMEOUT_SECS, "MX_GC_TIMEOUT_SECS");
391 assert_eq!(MODEL_EXPRESS_SECURITY_MODE, "MODEL_EXPRESS_SECURITY_MODE");
392 assert_eq!(
393 MODEL_EXPRESS_SECURITY_TOKEN_AUDIENCES,
394 "MODEL_EXPRESS_SECURITY_TOKEN_AUDIENCES"
395 );
396 assert_eq!(
397 MODEL_EXPRESS_SECURITY_ALLOWED_SERVICE_ACCOUNTS,
398 "MODEL_EXPRESS_SECURITY_ALLOWED_SERVICE_ACCOUNTS"
399 );
400 assert_eq!(
401 MODEL_EXPRESS_SECURITY_CACHE_TTL_SECS,
402 "MODEL_EXPRESS_SECURITY_CACHE_TTL_SECS"
403 );
404 assert_eq!(MX_AUTH_TOKEN_PATH, "MX_AUTH_TOKEN_PATH");
405 assert_eq!(MX_AUTH_TOKEN_TTL_SECONDS, "MX_AUTH_TOKEN_TTL_SECONDS");
406 assert_eq!(HOME, "HOME");
407 assert_eq!(USERPROFILE, "USERPROFILE");
408 assert_eq!(KUBECONFIG, "KUBECONFIG");
409 }
410
411 #[test]
412 fn hf_offline_parses_truthy_values() {
413 let lock = acquire_env_mutex();
414 for truthy in ["1", "on", "YES", "true", "True"] {
415 let _g = EnvVarGuard::set(&lock, HF_HUB_OFFLINE, truthy);
416 assert!(hf_offline(), "expected {truthy} to enable offline mode");
417 }
418 for falsey in ["0", "off", "no", "maybe"] {
419 let _g = EnvVarGuard::set(&lock, HF_HUB_OFFLINE, falsey);
420 assert!(!hf_offline(), "expected {falsey} to disable offline mode");
421 }
422 let _g = EnvVarGuard::remove(&lock, HF_HUB_OFFLINE);
423 assert!(!hf_offline(), "unset should disable offline mode");
424 }
425
426 #[test]
427 fn ngc_bases_default_then_override() {
428 let lock = acquire_env_mutex();
429 let _api = EnvVarGuard::remove(&lock, NGC_API_ENDPOINT);
430 let _authn = EnvVarGuard::remove(&lock, NGC_AUTH_ENDPOINT);
431 assert_eq!(ngc_api_base(), DEFAULT_NGC_API_BASE);
432 assert_eq!(ngc_authn_base(), DEFAULT_NGC_AUTHN_BASE);
433
434 let _api = EnvVarGuard::set(&lock, NGC_API_ENDPOINT, "https://api.example.com");
435 let _authn = EnvVarGuard::set(&lock, NGC_AUTH_ENDPOINT, "https://authn.example.com");
436 assert_eq!(ngc_api_base(), "https://api.example.com");
437 assert_eq!(ngc_authn_base(), "https://authn.example.com");
438 }
439
440 #[test]
441 fn ngc_api_key_prefers_primary_then_falls_back() {
442 let lock = acquire_env_mutex();
443 let _p = EnvVarGuard::set(&lock, NGC_API_KEY, " primary ");
444 let _s = EnvVarGuard::set(&lock, NGC_CLI_API_KEY, "secondary");
445 assert_eq!(ngc_api_key().as_deref(), Some("primary"));
446
447 let _p = EnvVarGuard::remove(&lock, NGC_API_KEY);
448 assert_eq!(ngc_api_key().as_deref(), Some("secondary"));
449
450 let _s = EnvVarGuard::remove(&lock, NGC_CLI_API_KEY);
451 assert_eq!(ngc_api_key(), None);
452 }
453
454 #[test]
455 fn redis_and_namespace_fallbacks() {
456 let lock = acquire_env_mutex();
457 let _h1 = EnvVarGuard::remove(&lock, MX_REDIS_HOST);
458 let _h2 = EnvVarGuard::set(&lock, REDIS_HOST, "legacy-host");
459 assert_eq!(redis_host().as_deref(), Some("legacy-host"));
460 let _h1 = EnvVarGuard::set(&lock, MX_REDIS_HOST, "mx-host");
461 assert_eq!(redis_host().as_deref(), Some("mx-host"));
462
463 let _n1 = EnvVarGuard::remove(&lock, MX_METADATA_NAMESPACE);
464 let _n2 = EnvVarGuard::set(&lock, POD_NAMESPACE, "pod-ns");
465 assert_eq!(metadata_namespace().as_deref(), Some("pod-ns"));
466 }
467
468 #[test]
469 fn reaper_getters_default_parse_and_fallback() {
470 let lock = acquire_env_mutex();
471 let _g = EnvVarGuard::remove(&lock, MX_REAPER_SCAN_INTERVAL_SECS);
472 assert_eq!(
473 reaper_scan_interval_secs(),
474 DEFAULT_REAPER_SCAN_INTERVAL_SECS
475 );
476
477 let _g = EnvVarGuard::set(&lock, MX_HEARTBEAT_TIMEOUT_SECS, "120");
478 assert_eq!(heartbeat_timeout_secs(), 120);
479
480 let _g = EnvVarGuard::set(&lock, MX_GC_TIMEOUT_SECS, "not-a-number");
481 assert_eq!(gc_timeout_secs(), DEFAULT_GC_TIMEOUT_SECS);
482 }
483}