@@ -212,16 +212,14 @@ impl FixtureImage {
212212// This fixture owns the only test in its binary. The wrapper's gateway is
213213// private to this run, so replacing its supervisor image cannot affect another
214214// test while the public fixture CA is installed in the supervisor trust store.
215- struct GatewayTrustConfig {
216- path : PathBuf ,
217- original : String ,
218- image_range : std:: ops:: Range < usize > ,
215+ struct GatewayTrustFixture {
216+ directory : PathBuf ,
219217 supervisor_image : String ,
220218 health_port : u16 ,
221219 restore_required : bool ,
222220}
223221
224- impl GatewayTrustConfig {
222+ impl GatewayTrustFixture {
225223 fn load ( ) -> Result < Self , String > {
226224 if std:: env:: var_os ( "OPENSHELL_GATEWAY_ENDPOINT" ) . is_some ( )
227225 || std:: env:: var_os ( "OPENSHELL_E2E_GATEWAY_BIN" ) . is_none ( )
@@ -233,6 +231,10 @@ impl GatewayTrustConfig {
233231 }
234232 let args_file = std:: env:: var_os ( "OPENSHELL_E2E_GATEWAY_ARGS_FILE" )
235233 . ok_or ( "managed gateway argument metadata is missing" ) ?;
234+ let directory = Path :: new ( & args_file)
235+ . parent ( )
236+ . ok_or ( "managed gateway arguments have no parent directory" ) ?
237+ . to_path_buf ( ) ;
236238 let raw =
237239 std:: fs:: read ( args_file) . map_err ( |_| "could not read managed gateway arguments" ) ?;
238240 let args = raw
@@ -249,112 +251,46 @@ impl GatewayTrustConfig {
249251 }
250252 Ok ( value)
251253 } ;
252- let path = PathBuf :: from ( argument ( "--config" ) ?) ;
253254 let health_port = argument ( "--health-port" ) ?
254255 . parse :: < u16 > ( )
255256 . map_err ( |_| "managed gateway health port is invalid" ) ?;
256- let original = std:: fs:: read_to_string ( & path)
257- . map_err ( |_| "could not read managed gateway configuration" ) ?;
258- let ( image_range, supervisor_image) = docker_supervisor_image ( & original) ?;
257+ let supervisor_image = std:: env:: var ( "OPENSHELL_SUPERVISOR_IMAGE" )
258+ . map_err ( |_| "managed supervisor image is missing" ) ?;
259259 Ok ( Self {
260- path,
261- original,
262- image_range,
260+ directory,
263261 supervisor_image,
264262 health_port,
265263 restore_required : false ,
266264 } )
267265 }
268266
269267 async fn apply ( & mut self , image : & str ) -> Result < ( ) , String > {
270- let mut updated = self . original . clone ( ) ;
271- updated. replace_range ( self . image_range . clone ( ) , image) ;
272- // Set the guard before the write: a failed write or restart must still
273- // flow through explicit restoration of the exact original bytes.
274268 self . restore_required = true ;
275- std:: fs:: write ( & self . path , updated)
276- . map_err ( |_| "could not install fixture supervisor configuration" ) ?;
277- restart_fixture_gateway ( self . health_port ) . await
269+ restart_fixture_gateway ( self . health_port , Some ( image) ) . await
278270 }
279271
280272 async fn restore ( & mut self ) -> Result < ( ) , String > {
281273 if !self . restore_required {
282274 return Ok ( ( ) ) ;
283275 }
284- std:: fs:: write ( & self . path , & self . original )
285- . map_err ( |_| "could not restore original gateway configuration" ) ?;
286- restart_fixture_gateway ( self . health_port )
276+ restart_fixture_gateway ( self . health_port , None )
287277 . await
288- . map_err ( |_| "original gateway configuration was restored but restart failed " ) ?;
278+ . map_err ( |_| "could not restore original gateway runtime " ) ?;
289279 self . restore_required = false ;
290280 Ok ( ( ) )
291281 }
292282}
293283
294- impl Drop for GatewayTrustConfig {
295- fn drop ( & mut self ) {
296- if self . restore_required {
297- // Cancellation/panic fallback restores disk state only. Normal
298- // Result paths explicitly restart and verify health; Drop never
299- // launches a subprocess or hides a failed restart as success.
300- let _ = std:: fs:: write ( & self . path , & self . original ) ;
301- }
302- }
303- }
304-
305- fn docker_supervisor_image ( config : & str ) -> Result < ( std:: ops:: Range < usize > , String ) , String > {
306- let mut in_docker = false ;
307- let mut offset = 0 ;
308- let mut found = None ;
309- for line in config. split_inclusive ( '\n' ) {
310- let trimmed = line. trim ( ) ;
311- if trimmed. starts_with ( '[' ) {
312- in_docker = trimmed == "[openshell.drivers.docker]" ;
313- } else if in_docker && let Some ( ( key, value) ) = trimmed. split_once ( '=' ) {
314- if key. trim ( ) == "socket_path" {
315- return Err (
316- "fixture cannot replace an external Docker driver configuration" . to_string ( ) ,
317- ) ;
318- }
319- if key. trim ( ) == "supervisor_image" {
320- // Accept only the wrapper's single-line quoted OCI reference.
321- // Reject escapes/comments instead of treating general TOML as
322- // text and accidentally changing a different configuration key.
323- let image = value
324- . trim ( )
325- . strip_prefix ( '"' )
326- . and_then ( |value| value. strip_suffix ( '"' ) )
327- . filter ( |image| {
328- !image. is_empty ( )
329- && image. bytes ( ) . all ( |byte| {
330- byte. is_ascii_alphanumeric ( ) || b"/:@._-" . contains ( & byte)
331- } )
332- } )
333- . ok_or ( "managed supervisor image is not a simple quoted OCI reference" ) ?;
334- let start = offset
335- + line
336- . find ( '"' )
337- . ok_or ( "managed supervisor image is not quoted" ) ?
338- + 1 ;
339- if found
340- . replace ( ( start..start + image. len ( ) , image. to_string ( ) ) )
341- . is_some ( )
342- {
343- return Err ( "managed Docker supervisor image is duplicated" . to_string ( ) ) ;
344- }
345- }
346- }
347- offset += line. len ( ) ;
348- }
349- found. ok_or_else ( || "managed Docker supervisor image is missing" . to_string ( ) )
350- }
351-
352- async fn restart_fixture_gateway ( health_port : u16 ) -> Result < ( ) , String > {
353- let gateway = ManagedGateway :: from_env ( )
284+ async fn restart_fixture_gateway (
285+ health_port : u16 ,
286+ supervisor_image : Option < & str > ,
287+ ) -> Result < ( ) , String > {
288+ let mut gateway = ManagedGateway :: from_env ( )
354289 . map_err ( |_| "could not load managed gateway restart metadata" ) ?
355290 . ok_or ( "managed gateway restart metadata disappeared" ) ?;
356- // ManagedGateway bounds graceful shutdown before force-kill. Keep it local:
357- // its Drop can start a stopped gateway, but never owns configuration restore.
291+ if let Some ( image) = supervisor_image {
292+ gateway. set_supervisor_image ( image) ;
293+ }
358294 gateway
359295 . stop ( )
360296 . map_err ( |_| "could not stop fixture gateway" ) ?;
@@ -1298,19 +1234,15 @@ fn check(
12981234#[ allow( clippy:: too_many_lines) ]
12991235async fn acknowledged_provider_changes_apply_to_fresh_clients_and_revoke_retained_references ( )
13001236-> Result < ( ) , String > {
1301- let mut gateway_config = GatewayTrustConfig :: load ( ) ?;
1237+ let mut gateway_fixture = GatewayTrustFixture :: load ( ) ?;
13021238 // Sandbox names are limited to 19 characters. Retain all 64 random bits
13031239 // within that limit so concurrent fixtures still own distinct resources.
13041240 let name = format ! ( "e2e{:016x}" , rand:: random:: <u64 >( ) ) ;
13051241 let mut backend = BackendPair :: new ( & name) ?;
13061242 // The wrapper's directory is shared with the host Docker daemon in CI;
13071243 // a job-container-local temporary path cannot back the TLS bind mount.
1308- let fixture_parent = gateway_config
1309- . path
1310- . parent ( )
1311- . ok_or ( "managed gateway configuration has no parent directory" ) ?;
1312- let directory =
1313- TempDir :: new_in ( fixture_parent) . map_err ( |_| "could not allocate fixture directory" ) ?;
1244+ let directory = TempDir :: new_in ( & gateway_fixture. directory )
1245+ . map_err ( |_| "could not allocate fixture directory" ) ?;
13141246 let context = directory. path ( ) . join ( "image" ) ;
13151247 std:: fs:: create_dir ( & context) . map_err ( |_| "could not allocate public image context" ) ?;
13161248 let backend_tls = directory. path ( ) . join ( "backend-tls" ) ;
@@ -1382,9 +1314,9 @@ async fn acknowledged_provider_changes_apply_to_fresh_clients_and_revoke_retaine
13821314 // user to the supervisor's private bootstrap files.
13831315 std:: fs:: write ( & supervisor_dockerfile, format ! (
13841316 "FROM {} AS supervisor\n FROM {} 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/readiness-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/readiness-fixture-ca.crt').read_bytes())\" ]\n FROM {}\n COPY --from=trust-bundle /tmp/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt\n " ,
1385- gateway_config . supervisor_image,
1317+ gateway_fixture . supervisor_image,
13861318 image. tag( ) ,
1387- gateway_config . supervisor_image,
1319+ gateway_fixture . supervisor_image,
13881320 ) ) . map_err ( |_| "could not write fixture supervisor Dockerfile" ) ?;
13891321 supervisor_image
13901322 . build (
@@ -1393,7 +1325,7 @@ async fn acknowledged_provider_changes_apply_to_fresh_clients_and_revoke_retaine
13931325 "build supervisor fixture image" ,
13941326 )
13951327 . await ?;
1396- gateway_config . apply ( supervisor_image. tag ( ) ) . await ?;
1328+ gateway_fixture . apply ( supervisor_image. tag ( ) ) . await ?;
13971329 let profile = directory. path ( ) . join ( "profile.json" ) ;
13981330 let policy = directory. path ( ) . join ( "policy.json" ) ;
13991331 write_profile ( & profile, & name, & host, port, python) ?;
@@ -1639,7 +1571,7 @@ async fn acknowledged_provider_changes_apply_to_fresh_clients_and_revoke_retaine
16391571 // Restore the original runtime before removing its replacement. Retain the
16401572 // derived supervisor image if restoration fails, and report that failure
16411573 // even when a lifecycle assertion already failed.
1642- let gateway_restore = gateway_config . restore ( ) . await ;
1574+ let gateway_restore = gateway_fixture . restore ( ) . await ;
16431575 let supervisor_cleanup = if gateway_restore. is_ok ( ) {
16441576 supervisor_image. remove ( ) . await
16451577 } else {
0 commit comments