@@ -354,49 +354,23 @@ impl Client {
354
354
// Upload layers
355
355
stream:: iter ( layers)
356
356
. map ( |layer| {
357
+ // This avoids moving `self` which is &mut Self
358
+ // into the async block. We only want to capture
359
+ // as &Self
357
360
let this = & self ;
358
361
async move {
359
362
let digest = layer. sha256_digest ( ) ;
360
- match this
361
- . push_blob_chunked ( image_ref, & layer. data , & digest)
362
- . await
363
- {
364
- Err ( OciDistributionError :: SpecViolationError ( violation) ) => {
365
- warn ! (
366
- ?violation,
367
- "Registry is not respecting the OCI Distribution \
368
- Specification when doing chunked push operations"
369
- ) ;
370
- warn ! ( "Attempting monolithic push" ) ;
371
- this. push_blob_monolithically ( image_ref, & layer. data , & digest)
372
- . await ?;
373
- }
374
- Err ( e) => return Err ( e) ,
375
- _ => { }
376
- } ;
377
-
378
- Ok ( ( ) )
363
+ this. push_blob ( image_ref, & layer. data , & digest) . await ?;
364
+ Result :: Ok ( ( ) )
379
365
}
380
366
} )
381
- . boxed ( ) // Workaround to rustc issue https://github.com/rust-lang/rust/issues/104382
382
367
. buffer_unordered ( self . config . max_concurrent_upload )
383
368
. try_for_each ( future:: ok)
384
369
. await ?;
385
370
386
- let config_url = match self
387
- . push_blob_chunked ( image_ref, & config. data , & manifest. config . digest )
388
- . await
389
- {
390
- Ok ( url) => url,
391
- Err ( OciDistributionError :: SpecViolationError ( violation) ) => {
392
- warn ! ( ?violation, "Registry is not respecting the OCI Distribution Specification when doing chunked push operations" ) ;
393
- warn ! ( "Attempting monolithic push" ) ;
394
- self . push_blob_monolithically ( image_ref, & config. data , & manifest. config . digest )
395
- . await ?
396
- }
397
- Err ( e) => return Err ( e) ,
398
- } ;
399
-
371
+ let config_url = self
372
+ . push_blob ( image_ref, & config. data , & manifest. config . digest )
373
+ . await ?;
400
374
let manifest_url = self . push_manifest ( image_ref, & manifest. into ( ) ) . await ?;
401
375
402
376
Ok ( PushResponse {
@@ -405,6 +379,24 @@ impl Client {
405
379
} )
406
380
}
407
381
382
+ /// Pushes a blob to the registry
383
+ pub async fn push_blob (
384
+ & self ,
385
+ image_ref : & Reference ,
386
+ data : & [ u8 ] ,
387
+ digest : & str ,
388
+ ) -> Result < String > {
389
+ match self . push_blob_chunked ( image_ref, data, digest) . await {
390
+ Ok ( url) => Ok ( url) ,
391
+ Err ( OciDistributionError :: SpecViolationError ( violation) ) => {
392
+ warn ! ( ?violation, "Registry is not respecting the OCI Distribution Specification when doing chunked push operations" ) ;
393
+ warn ! ( "Attempting monolithic push" ) ;
394
+ self . push_blob_monolithically ( image_ref, data, digest) . await
395
+ }
396
+ Err ( e) => Err ( e) ,
397
+ }
398
+ }
399
+
408
400
/// Pushes a blob to the registry as a monolith
409
401
///
410
402
/// Returns the pullable location of the blob
@@ -1002,6 +994,28 @@ impl Client {
1002
994
) )
1003
995
}
1004
996
997
+ /// Mounts a blob to the provided reference, from the given source
998
+ pub async fn mount_blob (
999
+ & self ,
1000
+ image : & Reference ,
1001
+ source : & Reference ,
1002
+ digest : & str ,
1003
+ ) -> Result < ( ) > {
1004
+ let base_url = self . to_v2_blob_upload_url ( image) ;
1005
+ let url = format ! ( "{}?mount={}&from={}" , base_url, digest, source. repository( ) ) ;
1006
+
1007
+ let res = RequestBuilderWrapper :: from_client ( self , |client| client. post ( url. clone ( ) ) )
1008
+ . apply_auth ( image, RegistryOperation :: Push ) ?
1009
+ . into_request_builder ( )
1010
+ . send ( )
1011
+ . await ?;
1012
+
1013
+ self . extract_location_header ( image, res, & reqwest:: StatusCode :: CREATED )
1014
+ . await ?;
1015
+
1016
+ Ok ( ( ) )
1017
+ }
1018
+
1005
1019
/// Pushes the manifest for a specified image
1006
1020
///
1007
1021
/// Returns pullable manifest URL
@@ -1018,12 +1032,30 @@ impl Client {
1018
1032
let mut ser = serde_json:: Serializer :: with_formatter ( & mut body, CanonicalFormatter :: new ( ) ) ;
1019
1033
manifest. serialize ( & mut ser) . unwrap ( ) ;
1020
1034
1035
+ self . push_manifest_raw ( image, body, manifest. content_type ( ) . parse ( ) . unwrap ( ) )
1036
+ . await
1037
+ }
1038
+
1039
+ /// Pushes the manifest, provided as raw bytes, for a specified image
1040
+ ///
1041
+ /// Returns pullable manifest url
1042
+ pub async fn push_manifest_raw (
1043
+ & self ,
1044
+ image : & Reference ,
1045
+ body : Vec < u8 > ,
1046
+ content_type : HeaderValue ,
1047
+ ) -> Result < String > {
1048
+ let url = self . to_v2_manifest_url ( image) ;
1049
+ debug ! ( ?url, ?content_type, "push manifest" ) ;
1050
+
1051
+ let mut headers = HeaderMap :: new ( ) ;
1052
+ headers. insert ( "Content-Type" , content_type) ;
1053
+
1021
1054
// Calculate the digest of the manifest, this is useful
1022
1055
// if the remote registry is violating the OCI Distribution Specification.
1023
1056
// See below for more details.
1024
1057
let manifest_hash = sha256_digest ( & body) ;
1025
1058
1026
- debug ! ( ?url, ?content_type, "push manifest" ) ;
1027
1059
let res = RequestBuilderWrapper :: from_client ( self , |client| client. put ( url. clone ( ) ) )
1028
1060
. apply_auth ( image, RegistryOperation :: Push ) ?
1029
1061
. into_request_builder ( )
@@ -2383,6 +2415,46 @@ mod test {
2383
2415
assert_eq ! ( manifest. config. digest, pulled_manifest. config. digest) ;
2384
2416
}
2385
2417
2418
+ #[ tokio:: test]
2419
+ #[ cfg( feature = "test-registry" ) ]
2420
+ async fn test_mount ( ) {
2421
+ // initialize the registry
2422
+ let docker = clients:: Cli :: default ( ) ;
2423
+ let test_container = docker. run ( registry_image ( ) ) ;
2424
+ let port = test_container. get_host_port_ipv4 ( 5000 ) ;
2425
+
2426
+ let mut c = Client :: new ( ClientConfig {
2427
+ protocol : ClientProtocol :: HttpsExcept ( vec ! [ format!( "localhost:{}" , port) ] ) ,
2428
+ ..Default :: default ( )
2429
+ } ) ;
2430
+
2431
+ // Create a dummy layer and push it to `layer-repository`
2432
+ let layer_reference: Reference = format ! ( "localhost:{}/layer-repository" , port)
2433
+ . parse ( )
2434
+ . unwrap ( ) ;
2435
+ let layer_data = vec ! [ 1u8 , 2 , 3 , 4 ] ;
2436
+ let layer_digest = sha256_digest ( & layer_data) ;
2437
+ c. push_blob ( & layer_reference, & [ 1 , 2 , 3 , 4 ] , & layer_digest)
2438
+ . await
2439
+ . expect ( "Failed to push" ) ;
2440
+
2441
+ // Mount the layer at `image-repository`
2442
+ let image_reference: Reference = format ! ( "localhost:{}/image-repository" , port)
2443
+ . parse ( )
2444
+ . unwrap ( ) ;
2445
+ c. mount_blob ( & image_reference, & layer_reference, & layer_digest)
2446
+ . await
2447
+ . expect ( "Failed to mount" ) ;
2448
+
2449
+ // Pull the layer from `image-repository`
2450
+ let mut buf = Vec :: new ( ) ;
2451
+ c. pull_blob ( & image_reference, & layer_digest, & mut buf)
2452
+ . await
2453
+ . expect ( "Failed to pull" ) ;
2454
+
2455
+ assert_eq ! ( layer_data, buf) ;
2456
+ }
2457
+
2386
2458
#[ tokio:: test]
2387
2459
async fn test_platform_resolution ( ) {
2388
2460
// test that we get an error when we pull a manifest list
0 commit comments