@@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
77use std:: fmt;
88use std:: net:: SocketAddr ;
99use std:: path:: PathBuf ;
10+ use std:: process:: Command ;
1011use std:: str:: FromStr ;
1112
1213// ── Public default constants ────────────────────────────────────────────
@@ -86,6 +87,40 @@ impl FromStr for ComputeDriverKind {
8687 }
8788}
8889
90+ /// Auto-detect the appropriate compute driver based on the runtime environment.
91+ ///
92+ /// Priority order: Kubernetes → Podman → Docker.
93+ /// VM is never auto-detected (requires explicit `--drivers vm`).
94+ ///
95+ /// Returns the first driver where the environment check passes.
96+ /// Returns `None` if no compatible driver is found.
97+ pub fn detect_driver ( ) -> Option < ComputeDriverKind > {
98+ // Kubernetes: check for KUBERNETES_SERVICE_HOST env var (set inside pods)
99+ if std:: env:: var_os ( "KUBERNETES_SERVICE_HOST" ) . is_some ( ) {
100+ return Some ( ComputeDriverKind :: Kubernetes ) ;
101+ }
102+
103+ // Podman: check if podman binary is available
104+ if is_binary_available ( "podman" ) {
105+ return Some ( ComputeDriverKind :: Podman ) ;
106+ }
107+
108+ // Docker: check if docker binary is available
109+ if is_binary_available ( "docker" ) {
110+ return Some ( ComputeDriverKind :: Docker ) ;
111+ }
112+
113+ None
114+ }
115+
116+ /// Check if a binary is available on the system PATH.
117+ fn is_binary_available ( name : & str ) -> bool {
118+ Command :: new ( name)
119+ . arg ( "--version" )
120+ . output ( )
121+ . is_ok_and ( |output| output. status . success ( ) )
122+ }
123+
89124/// Server configuration.
90125#[ derive( Debug , Clone , Serialize , Deserialize ) ]
91126pub struct Config {
@@ -124,7 +159,7 @@ pub struct Config {
124159 /// The config shape allows multiple drivers so the gateway can evolve
125160 /// toward multi-backend routing. Current releases require exactly one
126161 /// configured driver.
127- #[ serde( default = "default_compute_drivers" ) ]
162+ #[ serde( default ) ]
128163 pub compute_drivers : Vec < ComputeDriverKind > ,
129164
130165 /// Kubernetes namespace for sandboxes.
@@ -296,7 +331,7 @@ impl Config {
296331 tls,
297332 oidc : None ,
298333 database_url : String :: new ( ) ,
299- compute_drivers : default_compute_drivers ( ) ,
334+ compute_drivers : vec ! [ ] ,
300335 sandbox_namespace : default_sandbox_namespace ( ) ,
301336 sandbox_image : default_sandbox_image ( ) ,
302337 sandbox_image_pull_policy : String :: new ( ) ,
@@ -472,10 +507,6 @@ fn default_sandbox_image() -> String {
472507 format ! ( "{}/base:latest" , crate :: image:: DEFAULT_COMMUNITY_REGISTRY )
473508}
474509
475- fn default_compute_drivers ( ) -> Vec < ComputeDriverKind > {
476- vec ! [ ComputeDriverKind :: Kubernetes ]
477- }
478-
479510fn default_ssh_gateway_host ( ) -> String {
480511 "127.0.0.1" . to_string ( )
481512}
@@ -506,7 +537,7 @@ const fn default_ssh_session_ttl_secs() -> u64 {
506537
507538#[ cfg( test) ]
508539mod tests {
509- use super :: { ComputeDriverKind , Config } ;
540+ use super :: { ComputeDriverKind , Config , detect_driver } ;
510541 use std:: net:: SocketAddr ;
511542
512543 #[ test]
@@ -535,14 +566,6 @@ mod tests {
535566 assert ! ( err. contains( "unsupported compute driver 'firecracker'" ) ) ;
536567 }
537568
538- #[ test]
539- fn config_defaults_to_kubernetes_driver ( ) {
540- assert_eq ! (
541- Config :: new( None ) . compute_drivers,
542- vec![ ComputeDriverKind :: Kubernetes ]
543- ) ;
544- }
545-
546569 #[ test]
547570 fn config_new_disables_health_bind_by_default ( ) {
548571 let cfg = Config :: new ( None ) ;
@@ -555,4 +578,36 @@ mod tests {
555578 let cfg = Config :: new ( None ) . with_health_bind_address ( addr) ;
556579 assert_eq ! ( cfg. health_bind_address, Some ( addr) ) ;
557580 }
581+
582+ #[ test]
583+ fn detect_driver_returns_none_without_k8s_env_or_binaries ( ) {
584+ // When KUBERNETES_SERVICE_HOST is not set and no docker/podman binaries
585+ // are available, detect_driver should return None.
586+ // This test may pass or fail depending on the test environment,
587+ // but it documents the expected behavior.
588+ let _ = detect_driver ( ) ; // Returns Some or None based on environment
589+ }
590+
591+ #[ test]
592+ #[ allow( unsafe_code) ] // std::env::set_var/remove_var require unsafe in Rust 2024
593+ fn detect_driver_prefers_kubernetes_when_k8s_env_is_set ( ) {
594+ // Save the original env var
595+ let original = std:: env:: var ( "KUBERNETES_SERVICE_HOST" ) . ok ( ) ;
596+
597+ // Set the env var
598+ unsafe {
599+ std:: env:: set_var ( "KUBERNETES_SERVICE_HOST" , "127.0.0.1" ) ;
600+ }
601+
602+ let result = detect_driver ( ) ;
603+ assert_eq ! ( result, Some ( ComputeDriverKind :: Kubernetes ) ) ;
604+
605+ // Restore the original env var
606+ unsafe {
607+ match original {
608+ Some ( val) => std:: env:: set_var ( "KUBERNETES_SERVICE_HOST" , val) ,
609+ None => std:: env:: remove_var ( "KUBERNETES_SERVICE_HOST" ) ,
610+ }
611+ }
612+ }
558613}
0 commit comments