@@ -950,7 +950,7 @@ impl ImageProxy {
950950 ) -> Result < BlobStream < ' a > > {
951951 let fallback_to_get_blob = || async move {
952952 let ( reader, driver) = self . get_blob ( img, digest, expected_size) . await ?;
953- let driver = async move { driver. await } . boxed ( ) ;
953+ let driver = driver. boxed ( ) ;
954954 Ok ( BlobStream {
955955 source : BlobStreamSource :: GetBlob ,
956956 expected_size,
@@ -1317,6 +1317,130 @@ mod tests {
13171317 Ok ( ( ) )
13181318 }
13191319
1320+ #[ tokio:: test]
1321+ async fn test_get_blob_stream_oci_dir ( ) -> Result < ( ) > {
1322+ use std:: str:: FromStr ;
1323+
1324+ if !check_skopeo ( ) {
1325+ return Ok ( ( ) ) ;
1326+ }
1327+
1328+ fn sha256_digest ( bytes : & [ u8 ] ) -> Digest {
1329+ let mut h = sha2:: Sha256 :: new ( ) ;
1330+ h. update ( bytes) ;
1331+ Digest :: from_str ( & format ! ( "sha256:{}" , hex:: encode( h. finalize( ) ) ) ) . unwrap ( )
1332+ }
1333+
1334+ fn write_blob ( root : & std:: path:: Path , bytes : & [ u8 ] ) -> Result < ( Digest , u64 ) > {
1335+ let digest = sha256_digest ( bytes) ;
1336+ let size = bytes. len ( ) as u64 ;
1337+ let dir = root. join ( "blobs" ) . join ( "sha256" ) ;
1338+ std:: fs:: create_dir_all ( & dir) ?;
1339+ std:: fs:: write ( dir. join ( digest. digest ( ) ) , bytes) ?;
1340+ Ok ( ( digest, size) )
1341+ }
1342+
1343+ let td = tempfile:: tempdir ( ) ?;
1344+ std:: fs:: write (
1345+ td. path ( ) . join ( "oci-layout" ) ,
1346+ serde_json:: to_vec ( & serde_json:: json!( { "imageLayoutVersion" : "1.0.0" } ) ) ?,
1347+ ) ?;
1348+
1349+ let layer_bytes = b"layer bytes" ;
1350+ let ( layer_digest, layer_size) = write_blob ( td. path ( ) , layer_bytes) ?;
1351+
1352+ let config_bytes = serde_json:: to_vec ( & serde_json:: json!( {
1353+ "architecture" : "amd64" ,
1354+ "os" : "linux" ,
1355+ "rootfs" : {
1356+ "type" : "layers" ,
1357+ "diff_ids" : [ layer_digest. to_string( ) ] ,
1358+ } ,
1359+ "config" : { } ,
1360+ } ) ) ?;
1361+ let ( config_digest, config_size) = write_blob ( td. path ( ) , & config_bytes) ?;
1362+
1363+ let manifest_bytes = serde_json:: to_vec ( & serde_json:: json!( {
1364+ "schemaVersion" : 2 ,
1365+ "mediaType" : "application/vnd.oci.image.manifest.v1+json" ,
1366+ "config" : {
1367+ "mediaType" : "application/vnd.oci.image.config.v1+json" ,
1368+ "digest" : config_digest. to_string( ) ,
1369+ "size" : config_size,
1370+ } ,
1371+ "layers" : [ {
1372+ "mediaType" : "application/vnd.oci.image.layer.v1.tar" ,
1373+ "digest" : layer_digest. to_string( ) ,
1374+ "size" : layer_size,
1375+ } ] ,
1376+ } ) ) ?;
1377+ let ( manifest_digest, manifest_size) = write_blob ( td. path ( ) , & manifest_bytes) ?;
1378+
1379+ std:: fs:: write (
1380+ td. path ( ) . join ( "index.json" ) ,
1381+ serde_json:: to_vec ( & serde_json:: json!( {
1382+ "schemaVersion" : 2 ,
1383+ "mediaType" : "application/vnd.oci.image.index.v1+json" ,
1384+ "manifests" : [ {
1385+ "mediaType" : "application/vnd.oci.image.manifest.v1+json" ,
1386+ "digest" : manifest_digest. to_string( ) ,
1387+ "size" : manifest_size,
1388+ "annotations" : {
1389+ "org.opencontainers.image.ref.name" : "test" ,
1390+ }
1391+ } ]
1392+ } ) ) ?,
1393+ ) ?;
1394+
1395+ let proxy = ImageProxy :: new ( ) . await ?;
1396+ let imgref = format ! ( "oci:{}:test" , td. path( ) . display( ) ) ;
1397+ let img = proxy. open_image ( & imgref) . await ?;
1398+
1399+ let expected_source = match std:: env:: var ( "EXPECT_BLOB_STREAM_SOURCE" ) . ok ( ) . as_deref ( ) {
1400+ Some ( "GetRawBlob" ) => BlobStreamSource :: GetRawBlob ,
1401+ Some ( "GetBlob" ) => BlobStreamSource :: GetBlob ,
1402+ Some ( v) => {
1403+ return Err ( Error :: Other (
1404+ format ! (
1405+ "Invalid EXPECT_BLOB_STREAM_SOURCE={v}; expected GetRawBlob or GetBlob"
1406+ )
1407+ . into ( ) ,
1408+ ) ) ;
1409+ }
1410+ None => {
1411+ if proxy. supports_get_raw_blob ( ) {
1412+ BlobStreamSource :: GetRawBlob
1413+ } else {
1414+ BlobStreamSource :: GetBlob
1415+ }
1416+ }
1417+ } ;
1418+
1419+ let BlobStream {
1420+ source,
1421+ reader,
1422+ driver,
1423+ ..
1424+ } = proxy
1425+ . get_blob_stream ( & img, & layer_digest, layer_size)
1426+ . await ?;
1427+ assert_eq ! ( source, expected_source) ;
1428+
1429+ let mut reader = reader;
1430+ let mut sink = tokio:: io:: sink ( ) ;
1431+ let read = async move {
1432+ let n = tokio:: io:: copy ( & mut * reader, & mut sink) . await ?;
1433+ Result :: Ok ( n)
1434+ } ;
1435+ let ( n, driver) = tokio:: join!( read, driver) ;
1436+ assert_eq ! ( n?, layer_size) ;
1437+ driver?;
1438+
1439+ proxy. close_image ( & img) . await ?;
1440+ proxy. finalize ( ) . await ?;
1441+ Ok ( ( ) )
1442+ }
1443+
13201444 // Helper to create a dummy OwnedFd using memfd_create for testing.
13211445 fn create_dummy_fd ( ) -> OwnedFd {
13221446 memfd_create ( c"test-fd" , MemfdFlags :: CLOEXEC ) . unwrap ( )
0 commit comments