@@ -18,8 +18,8 @@ use crate::compute::driver_config::GuestTlsPaths;
1818use crate :: config_file:: { self , ConfigFile , GatewayFileSection } ;
1919use crate :: defaults:: { self , LocalTlsPaths } ;
2020use crate :: {
21- ServerStartupConfig , configured_compute_driver_for_startup , run_server ,
22- tracing_bus:: TracingLogBus ,
21+ ComputeDriverRegistry , ServerStartupConfig , configured_compute_driver_for_startup ,
22+ install_default_compute_drivers , run_server , tracing_bus:: TracingLogBus ,
2323} ;
2424
2525/// `OpenShell` gateway process - gRPC and HTTP server with protocol multiplexing.
@@ -223,6 +223,11 @@ pub fn command() -> Command {
223223}
224224
225225pub async fn run_cli ( ) -> Result < ( ) > {
226+ run_cli_with_compute_drivers ( install_default_compute_drivers ( ) ) . await
227+ }
228+
229+ /// Run the gateway CLI with the compute drivers linked by the binary.
230+ pub async fn run_cli_with_compute_drivers ( compute_drivers : ComputeDriverRegistry ) -> Result < ( ) > {
226231 rustls:: crypto:: ring:: default_provider ( )
227232 . install_default ( )
228233 . map_err ( |e| miette:: miette!( "failed to install rustls crypto provider: {e:?}" ) ) ?;
@@ -232,11 +237,15 @@ pub async fn run_cli() -> Result<()> {
232237
233238 match cli. command {
234239 Some ( Commands :: GenerateCerts ( args) ) => certgen:: run ( args) . await ,
235- None => Box :: pin ( run_from_args ( cli. run , matches) ) . await ,
240+ None => Box :: pin ( run_from_args ( cli. run , matches, compute_drivers ) ) . await ,
236241 }
237242}
238243
239- fn prepare_server_config ( args : & mut RunArgs , matches : & ArgMatches ) -> Result < ServerStartupConfig > {
244+ fn prepare_server_config (
245+ args : & mut RunArgs ,
246+ matches : & ArgMatches ,
247+ compute_drivers : & ComputeDriverRegistry ,
248+ ) -> Result < ServerStartupConfig > {
240249 // Load TOML when explicitly requested, or from the default XDG location
241250 // when that file exists. Missing default config is not an error: runtime
242251 // defaults and OPENSHELL_* env vars are enough for package-managed starts.
@@ -250,6 +259,10 @@ fn prepare_server_config(args: &mut RunArgs, matches: &ArgMatches) -> Result<Ser
250259 merge_file_into_args ( args, & file. openshell . gateway , matches) ;
251260 }
252261 normalize_compute_driver_socket_args ( args, matches) ?;
262+ let compute_driver = compute_drivers
263+ . select ( & args. drivers )
264+ . map_err ( |error| miette:: miette!( "{error}" ) ) ?;
265+ let compute_driver_kind = compute_driver. name ( ) . parse :: < ComputeDriverKind > ( ) . ok ( ) ;
253266
254267 let local_tls = apply_runtime_defaults ( args) ?;
255268 let guest_tls = local_tls. as_ref ( ) . map ( GuestTlsPaths :: from) ;
@@ -259,7 +272,8 @@ fn prepare_server_config(args: &mut RunArgs, matches: &ArgMatches) -> Result<Ser
259272
260273 let has_client_ca = args. tls_client_ca . is_some ( ) ;
261274 let has_oidc = args. oidc_issuer . is_some ( ) ;
262- let mtls_auth_enabled = resolve_mtls_auth_enabled ( args, matches, file. as_ref ( ) ) ;
275+ let mtls_auth_enabled =
276+ resolve_mtls_auth_enabled ( args, matches, file. as_ref ( ) , compute_driver_kind) ;
263277
264278 if args. disable_tls && has_client_ca {
265279 return Err ( miette:: miette!(
@@ -276,12 +290,7 @@ fn prepare_server_config(args: &mut RunArgs, matches: &ArgMatches) -> Result<Ser
276290 "mTLS user authentication requires --tls-client-ca so client certificates can be verified."
277291 ) ) ;
278292 }
279- if mtls_auth_enabled
280- && matches ! (
281- effective_single_driver( args) ,
282- Some ( ComputeDriverKind :: Kubernetes )
283- )
284- {
293+ if mtls_auth_enabled && matches ! ( compute_driver_kind, Some ( ComputeDriverKind :: Kubernetes ) ) {
285294 return Err ( miette:: miette!(
286295 "mTLS user authentication is not supported with the Kubernetes compute driver. Configure OIDC or a trusted fronting proxy for user authentication."
287296 ) ) ;
@@ -469,12 +478,17 @@ fn prepare_server_config(args: &mut RunArgs, matches: &ArgMatches) -> Result<Ser
469478 config,
470479 config_file : file,
471480 guest_tls,
481+ compute_driver,
472482 } )
473483}
474484
475- async fn run_from_args ( mut args : RunArgs , matches : ArgMatches ) -> Result < ( ) > {
476- let prepared = prepare_server_config ( & mut args, & matches) ?;
477- let compute_driver = configured_compute_driver_for_startup ( & prepared) ?;
485+ async fn run_from_args (
486+ mut args : RunArgs ,
487+ matches : ArgMatches ,
488+ compute_drivers : ComputeDriverRegistry ,
489+ ) -> Result < ( ) > {
490+ let prepared = prepare_server_config ( & mut args, & matches, & compute_drivers) ?;
491+ let compute_driver = configured_compute_driver_for_startup ( & compute_drivers, & prepared) ?;
478492
479493 let tracing_log_bus = TracingLogBus :: new ( ) ;
480494 let otlp_config = prepared
@@ -779,17 +793,9 @@ fn normalize_compute_driver_socket_args(args: &mut RunArgs, matches: &ArgMatches
779793 }
780794}
781795
782- fn effective_single_driver ( args : & RunArgs ) -> Option < ComputeDriverKind > {
783- match args. drivers . as_slice ( ) {
784- [ ] => openshell_core:: config:: detect_driver ( ) ,
785- [ driver] => driver. parse ( ) . ok ( ) ,
786- _ => None ,
787- }
788- }
789-
790- fn is_singleplayer_driver ( args : & RunArgs ) -> bool {
796+ fn is_singleplayer_driver ( driver : Option < ComputeDriverKind > ) -> bool {
791797 matches ! (
792- effective_single_driver ( args ) ,
798+ driver ,
793799 Some ( ComputeDriverKind :: Docker | ComputeDriverKind :: Podman | ComputeDriverKind :: Vm )
794800 )
795801}
@@ -798,6 +804,7 @@ fn resolve_mtls_auth_enabled(
798804 args : & RunArgs ,
799805 matches : & ArgMatches ,
800806 file : Option < & ConfigFile > ,
807+ compute_driver : Option < ComputeDriverKind > ,
801808) -> bool {
802809 let file_configured = file
803810 . and_then ( |f| f. openshell . gateway . mtls_auth . as_ref ( ) )
@@ -810,7 +817,7 @@ fn resolve_mtls_auth_enabled(
810817 return false ;
811818 }
812819
813- is_singleplayer_driver ( args )
820+ is_singleplayer_driver ( compute_driver )
814821}
815822
816823#[ cfg( test) ]
@@ -819,6 +826,43 @@ mod tests {
819826 use crate :: TEST_ENV_LOCK as ENV_LOCK ;
820827 use clap:: Parser ;
821828 use std:: net:: { IpAddr , Ipv4Addr } ;
829+ use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
830+
831+ static REGISTRY_DETECTION_CALLS : AtomicUsize = AtomicUsize :: new ( 0 ) ;
832+
833+ fn detect_registered_docker ( ) -> bool {
834+ REGISTRY_DETECTION_CALLS . fetch_add ( 1 , Ordering :: SeqCst ) ;
835+ true
836+ }
837+
838+ #[ derive( Clone , Copy ) ]
839+ struct TestComputeDriverFactory ;
840+
841+ #[ async_trait:: async_trait]
842+ impl crate :: ComputeDriverFactory for TestComputeDriverFactory {
843+ async fn build (
844+ & self ,
845+ _context : crate :: ComputeDriverBuildContext < ' _ > ,
846+ ) -> openshell_core:: Result < crate :: ComputeDriverBuildOutput > {
847+ unreachable ! ( "configuration tests do not construct the driver" )
848+ }
849+ }
850+
851+ fn detected_docker_registry ( ) -> crate :: ComputeDriverRegistry {
852+ let mut registry = crate :: ComputeDriverRegistry :: new ( ) ;
853+ registry
854+ . install (
855+ crate :: ComputeDriverRegistration :: new (
856+ "docker" ,
857+ 100 ,
858+ Some ( detect_registered_docker) ,
859+ TestComputeDriverFactory ,
860+ )
861+ . unwrap ( ) ,
862+ )
863+ . unwrap ( ) ;
864+ registry
865+ }
822866
823867 struct EnvVarGuard {
824868 key : & ' static str ,
@@ -1286,7 +1330,47 @@ mod tests {
12861330 "/tmp/ca.crt" ,
12871331 ] ) ;
12881332
1289- assert ! ( super :: resolve_mtls_auth_enabled( & args, & matches, None ) ) ;
1333+ assert ! ( super :: resolve_mtls_auth_enabled(
1334+ & args,
1335+ & matches,
1336+ None ,
1337+ Some ( openshell_core:: ComputeDriverKind :: Docker )
1338+ ) ) ;
1339+ }
1340+
1341+ #[ test]
1342+ fn registry_detection_drives_auth_defaults_once ( ) {
1343+ let _lock = ENV_LOCK
1344+ . lock ( )
1345+ . unwrap_or_else ( std:: sync:: PoisonError :: into_inner) ;
1346+ let state = tempfile:: tempdir ( ) . unwrap ( ) ;
1347+ let config = tempfile:: tempdir ( ) . unwrap ( ) ;
1348+ let _state = EnvVarGuard :: set ( "XDG_STATE_HOME" , state. path ( ) . to_str ( ) . unwrap ( ) ) ;
1349+ let _config = EnvVarGuard :: set ( "XDG_CONFIG_HOME" , config. path ( ) . to_str ( ) . unwrap ( ) ) ;
1350+ let _kubernetes = EnvVarGuard :: set ( "KUBERNETES_SERVICE_HOST" , "10.0.0.1" ) ;
1351+ let _mtls = EnvVarGuard :: remove ( "OPENSHELL_ENABLE_MTLS_AUTH" ) ;
1352+ let _drivers = EnvVarGuard :: remove ( "OPENSHELL_DRIVERS" ) ;
1353+ REGISTRY_DETECTION_CALLS . store ( 0 , Ordering :: SeqCst ) ;
1354+
1355+ let ( mut args, matches) = parse_with_args ( & [
1356+ "openshell-gateway" ,
1357+ "--db-url" ,
1358+ "sqlite::memory:" ,
1359+ "--tls-cert" ,
1360+ "/tmp/server.crt" ,
1361+ "--tls-key" ,
1362+ "/tmp/server.key" ,
1363+ "--tls-client-ca" ,
1364+ "/tmp/ca.crt" ,
1365+ ] ) ;
1366+ let registry = detected_docker_registry ( ) ;
1367+
1368+ let prepared = super :: prepare_server_config ( & mut args, & matches, & registry) . unwrap ( ) ;
1369+
1370+ assert_eq ! ( prepared. compute_driver. name( ) , "docker" ) ;
1371+ assert ! ( prepared. config. compute_drivers. is_empty( ) ) ;
1372+ assert ! ( prepared. config. mtls_auth. enabled) ;
1373+ assert_eq ! ( REGISTRY_DETECTION_CALLS . load( Ordering :: SeqCst ) , 1 ) ;
12901374 }
12911375
12921376 #[ test]
@@ -1310,7 +1394,12 @@ mod tests {
13101394 "/tmp/ca.crt" ,
13111395 ] ) ;
13121396
1313- assert ! ( !super :: resolve_mtls_auth_enabled( & args, & matches, None ) ) ;
1397+ assert ! ( !super :: resolve_mtls_auth_enabled(
1398+ & args,
1399+ & matches,
1400+ None ,
1401+ Some ( openshell_core:: ComputeDriverKind :: Kubernetes )
1402+ ) ) ;
13141403 }
13151404
13161405 #[ test]
@@ -1345,7 +1434,8 @@ enabled = false
13451434 assert ! ( !super :: resolve_mtls_auth_enabled(
13461435 & args,
13471436 & matches,
1348- Some ( & file)
1437+ Some ( & file) ,
1438+ Some ( openshell_core:: ComputeDriverKind :: Docker )
13491439 ) ) ;
13501440 }
13511441
@@ -1573,37 +1663,21 @@ ssh_session_ttl_secs = 1234
15731663
15741664 #[ test]
15751665 fn singleplayer_driver_matches_only_one_local_driver ( ) {
1576- for driver in [ "docker" , "podman" , "vm" ] {
1577- let ( args, _) = parse_with_args ( & [
1578- "openshell-gateway" ,
1579- "--db-url" ,
1580- "sqlite::memory:" ,
1581- "--drivers" ,
1582- driver,
1583- ] ) ;
1666+ for driver in [
1667+ openshell_core:: ComputeDriverKind :: Docker ,
1668+ openshell_core:: ComputeDriverKind :: Podman ,
1669+ openshell_core:: ComputeDriverKind :: Vm ,
1670+ ] {
15841671 assert ! (
1585- super :: is_singleplayer_driver( & args ) ,
1672+ super :: is_singleplayer_driver( Some ( driver ) ) ,
15861673 "{driver} should be singleplayer"
15871674 ) ;
15881675 }
15891676
1590- let ( k8s, _) = parse_with_args ( & [
1591- "openshell-gateway" ,
1592- "--db-url" ,
1593- "sqlite::memory:" ,
1594- "--drivers" ,
1595- "kubernetes" ,
1596- ] ) ;
1597- assert ! ( !super :: is_singleplayer_driver( & k8s) ) ;
1598-
1599- let ( multi, _) = parse_with_args ( & [
1600- "openshell-gateway" ,
1601- "--db-url" ,
1602- "sqlite::memory:" ,
1603- "--drivers" ,
1604- "docker,podman" ,
1605- ] ) ;
1606- assert ! ( !super :: is_singleplayer_driver( & multi) ) ;
1677+ assert ! ( !super :: is_singleplayer_driver( Some (
1678+ openshell_core:: ComputeDriverKind :: Kubernetes
1679+ ) ) ) ;
1680+ assert ! ( !super :: is_singleplayer_driver( None ) ) ;
16071681 }
16081682
16091683 #[ test]
@@ -1629,7 +1703,11 @@ ssh_session_ttl_secs = 1234
16291703 Some ( std:: path:: Path :: new( "/run/openshell/kyma.sock" ) )
16301704 ) ;
16311705 assert_eq ! ( args. drivers, [ "kyma" ] ) ;
1632- assert ! ( super :: effective_single_driver( & args) . is_none( ) ) ;
1706+ assert ! (
1707+ args. drivers[ 0 ]
1708+ . parse:: <openshell_core:: ComputeDriverKind >( )
1709+ . is_err( )
1710+ ) ;
16331711 }
16341712
16351713 #[ test]
@@ -1809,8 +1887,12 @@ mem_mib = "not-a-number"
18091887 "--disable-tls" ,
18101888 ] ) ;
18111889
1812- let prepared =
1813- super :: prepare_server_config ( & mut args, & matches) . expect ( "server config is prepared" ) ;
1890+ let prepared = super :: prepare_server_config (
1891+ & mut args,
1892+ & matches,
1893+ & crate :: install_default_compute_drivers ( ) ,
1894+ )
1895+ . expect ( "server config is prepared" ) ;
18141896
18151897 assert_eq ! ( prepared. config. compute_drivers, vec![ "podman" . to_string( ) ] ) ;
18161898 assert_eq ! (
0 commit comments