@@ -247,16 +247,14 @@ impl FixtureImage {
247247
248248// These tests run serially against a wrapper-owned gateway. The public fixture
249249// CA must be removed and the original supervisor restored before the next case.
250- struct GatewayTrustConfig {
251- path : PathBuf ,
252- original : String ,
253- image_range : std:: ops:: Range < usize > ,
250+ struct GatewayTrustFixture {
251+ directory : PathBuf ,
254252 supervisor_image : String ,
255253 health_port : u16 ,
256254 restore_required : bool ,
257255}
258256
259- impl GatewayTrustConfig {
257+ impl GatewayTrustFixture {
260258 fn load ( ) -> Result < Self , String > {
261259 if std:: env:: var_os ( "OPENSHELL_GATEWAY_ENDPOINT" ) . is_some ( )
262260 || std:: env:: var_os ( "OPENSHELL_E2E_GATEWAY_BIN" ) . is_none ( )
@@ -268,6 +266,10 @@ impl GatewayTrustConfig {
268266 }
269267 let args_file = std:: env:: var_os ( "OPENSHELL_E2E_GATEWAY_ARGS_FILE" )
270268 . ok_or ( "managed gateway argument metadata is missing" ) ?;
269+ let directory = Path :: new ( & args_file)
270+ . parent ( )
271+ . ok_or ( "managed gateway arguments have no parent directory" ) ?
272+ . to_path_buf ( ) ;
271273 let raw =
272274 std:: fs:: read ( args_file) . map_err ( |_| "could not read managed gateway arguments" ) ?;
273275 let args = raw
@@ -284,112 +286,46 @@ impl GatewayTrustConfig {
284286 }
285287 Ok ( value)
286288 } ;
287- let path = PathBuf :: from ( argument ( "--config" ) ?) ;
288289 let health_port = argument ( "--health-port" ) ?
289290 . parse :: < u16 > ( )
290291 . map_err ( |_| "managed gateway health port is invalid" ) ?;
291- let original = std:: fs:: read_to_string ( & path)
292- . map_err ( |_| "could not read managed gateway configuration" ) ?;
293- let ( image_range, supervisor_image) = docker_supervisor_image ( & original) ?;
292+ let supervisor_image = std:: env:: var ( "OPENSHELL_SUPERVISOR_IMAGE" )
293+ . map_err ( |_| "managed supervisor image is missing" ) ?;
294294 Ok ( Self {
295- path,
296- original,
297- image_range,
295+ directory,
298296 supervisor_image,
299297 health_port,
300298 restore_required : false ,
301299 } )
302300 }
303301
304302 async fn apply ( & mut self , image : & str ) -> Result < ( ) , String > {
305- let mut updated = self . original . clone ( ) ;
306- updated. replace_range ( self . image_range . clone ( ) , image) ;
307- // Set the guard before the write: a failed write or restart must still
308- // flow through explicit restoration of the exact original bytes.
309303 self . restore_required = true ;
310- std:: fs:: write ( & self . path , updated)
311- . map_err ( |_| "could not install fixture supervisor configuration" ) ?;
312- restart_fixture_gateway ( self . health_port ) . await
304+ restart_fixture_gateway ( self . health_port , Some ( image) ) . await
313305 }
314306
315307 async fn restore ( & mut self ) -> Result < ( ) , String > {
316308 if !self . restore_required {
317309 return Ok ( ( ) ) ;
318310 }
319- std:: fs:: write ( & self . path , & self . original )
320- . map_err ( |_| "could not restore original gateway configuration" ) ?;
321- restart_fixture_gateway ( self . health_port )
311+ restart_fixture_gateway ( self . health_port , None )
322312 . await
323- . map_err ( |_| "original gateway configuration was restored but restart failed " ) ?;
313+ . map_err ( |_| "could not restore original gateway runtime " ) ?;
324314 self . restore_required = false ;
325315 Ok ( ( ) )
326316 }
327317}
328318
329- impl Drop for GatewayTrustConfig {
330- fn drop ( & mut self ) {
331- if self . restore_required {
332- // Cancellation/panic fallback restores disk state only. Normal
333- // Result paths explicitly restart and verify health; Drop never
334- // launches a subprocess or hides a failed restart as success.
335- let _ = std:: fs:: write ( & self . path , & self . original ) ;
336- }
337- }
338- }
339-
340- fn docker_supervisor_image ( config : & str ) -> Result < ( std:: ops:: Range < usize > , String ) , String > {
341- let mut in_docker = false ;
342- let mut offset = 0 ;
343- let mut found = None ;
344- for line in config. split_inclusive ( '\n' ) {
345- let trimmed = line. trim ( ) ;
346- if trimmed. starts_with ( '[' ) {
347- in_docker = trimmed == "[openshell.drivers.docker]" ;
348- } else if in_docker && let Some ( ( key, value) ) = trimmed. split_once ( '=' ) {
349- if key. trim ( ) == "socket_path" {
350- return Err (
351- "fixture cannot replace an external Docker driver configuration" . to_string ( ) ,
352- ) ;
353- }
354- if key. trim ( ) == "supervisor_image" {
355- // Accept only the wrapper's single-line quoted OCI reference.
356- // Reject escapes/comments instead of treating general TOML as
357- // text and accidentally changing a different configuration key.
358- let image = value
359- . trim ( )
360- . strip_prefix ( '"' )
361- . and_then ( |value| value. strip_suffix ( '"' ) )
362- . filter ( |image| {
363- !image. is_empty ( )
364- && image. bytes ( ) . all ( |byte| {
365- byte. is_ascii_alphanumeric ( ) || b"/:@._-" . contains ( & byte)
366- } )
367- } )
368- . ok_or ( "managed supervisor image is not a simple quoted OCI reference" ) ?;
369- let start = offset
370- + line
371- . find ( '"' )
372- . ok_or ( "managed supervisor image is not quoted" ) ?
373- + 1 ;
374- if found
375- . replace ( ( start..start + image. len ( ) , image. to_string ( ) ) )
376- . is_some ( )
377- {
378- return Err ( "managed Docker supervisor image is duplicated" . to_string ( ) ) ;
379- }
380- }
381- }
382- offset += line. len ( ) ;
383- }
384- found. ok_or_else ( || "managed Docker supervisor image is missing" . to_string ( ) )
385- }
386-
387- async fn restart_fixture_gateway ( health_port : u16 ) -> Result < ( ) , String > {
388- let gateway = ManagedGateway :: from_env ( )
319+ async fn restart_fixture_gateway (
320+ health_port : u16 ,
321+ supervisor_image : Option < & str > ,
322+ ) -> Result < ( ) , String > {
323+ let mut gateway = ManagedGateway :: from_env ( )
389324 . map_err ( |_| "could not load managed gateway restart metadata" ) ?
390325 . ok_or ( "managed gateway restart metadata disappeared" ) ?;
391- // ManagedGateway bounds graceful shutdown before force-kill. Keep it local:
392- // its Drop can start a stopped gateway, but never owns configuration restore.
326+ if let Some ( image) = supervisor_image {
327+ gateway. set_supervisor_image ( image) ;
328+ }
393329 gateway
394330 . stop ( )
395331 . map_err ( |_| "could not stop fixture gateway" ) ?;
@@ -1103,17 +1039,13 @@ async fn ordinary_external_placeholder_keeps_revoked_assertion_after_rotation()
11031039// Keep each lifecycle together so no phase can replace the retained client.
11041040#[ allow( clippy:: too_many_lines) ]
11051041async fn external_caller_assertion_rotation ( stable : bool ) -> Result < ( ) , String > {
1106- let mut gateway_config = GatewayTrustConfig :: load ( ) ?;
1042+ let mut gateway_fixture = GatewayTrustFixture :: load ( ) ?;
11071043 let name = format ! ( "e2e{:016x}" , rand:: random:: <u64 >( ) ) ;
11081044 let mut backend = BackendPair :: new ( & name) ?;
11091045 // The wrapper's directory is shared with the host Docker daemon in CI;
11101046 // a job-container-local temporary path cannot back the TLS bind mount.
1111- let fixture_parent = gateway_config
1112- . path
1113- . parent ( )
1114- . ok_or ( "managed gateway configuration has no parent directory" ) ?;
1115- let directory =
1116- TempDir :: new_in ( fixture_parent) . map_err ( |_| "could not allocate fixture directory" ) ?;
1047+ let directory = TempDir :: new_in ( & gateway_fixture. directory )
1048+ . map_err ( |_| "could not allocate fixture directory" ) ?;
11171049 let context = directory. path ( ) . join ( "image" ) ;
11181050 std:: fs:: create_dir ( & context) . map_err ( |_| "could not allocate public image context" ) ?;
11191051 let backend_tls = directory. path ( ) . join ( "backend-tls" ) ;
@@ -1141,8 +1073,8 @@ async fn external_caller_assertion_rotation(stable: bool) -> Result<(), String>
11411073 // default user and its original public trust roots.
11421074 std:: fs:: write ( & supervisor_dockerfile, format ! (
11431075 "FROM {} AS supervisor\n FROM {base} AS trust-bundle\n USER 0\n COPY --from=supervisor /etc/ssl/certs/ca-certificates.crt /tmp/ca-certificates.crt\n COPY fixture-ca.crt /tmp/stable-fixture-ca.crt\n RUN [\" /usr/bin/python3\" , \" -c\" , \" from pathlib import Path; bundle = Path('/tmp/ca-certificates.crt'); bundle.write_bytes(bundle.read_bytes() + Path('/tmp/stable-fixture-ca.crt').read_bytes())\" ]\n FROM {}\n COPY --from=trust-bundle /tmp/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt\n " ,
1144- gateway_config . supervisor_image,
1145- gateway_config . supervisor_image
1076+ gateway_fixture . supervisor_image,
1077+ gateway_fixture . supervisor_image
11461078 ) ) . map_err ( |_| "could not write fixture supervisor Dockerfile" ) ?;
11471079 let image = FixtureImage :: new ( ) ?;
11481080 let supervisor_image = FixtureImage :: new ( ) ?;
@@ -1191,7 +1123,7 @@ async fn external_caller_assertion_rotation(stable: bool) -> Result<(), String>
11911123 supervisor_image
11921124 . build ( & supervisor_dockerfile, & context)
11931125 . await ?;
1194- gateway_config . apply ( supervisor_image. tag ( ) ) . await ?;
1126+ gateway_fixture . apply ( supervisor_image. tag ( ) ) . await ?;
11951127 write_profile ( & profile, & name, & host, port, python, stable) ?;
11961128 write_policy ( & policy, & host, & other_host, port, other_port, python) ?;
11971129 let configuration = json ! ( { "host" : host, "other_host" : other_host, "port" : port,
@@ -1397,7 +1329,7 @@ async fn external_caller_assertion_rotation(stable: bool) -> Result<(), String>
13971329 // Restore the original runtime before removing its replacement. Retain the
13981330 // derived supervisor image if restoration fails, and report that failure
13991331 // even when a lifecycle assertion already failed.
1400- let gateway_restore = gateway_config . restore ( ) . await ;
1332+ let gateway_restore = gateway_fixture . restore ( ) . await ;
14011333 let supervisor_cleanup = if gateway_restore. is_ok ( ) {
14021334 supervisor_image. remove ( ) . await
14031335 } else {
0 commit comments