Skip to content

Commit 1c511f2

Browse files
aochagaviaflavio
authored andcommitted
Enhance client to push blobs, mount blobs, and push raw manifests
Signed-off-by: Adolfo Ochagavía <[email protected]>
1 parent 1441622 commit 1c511f2

File tree

1 file changed

+107
-35
lines changed

1 file changed

+107
-35
lines changed

src/client.rs

Lines changed: 107 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -354,49 +354,23 @@ impl Client {
354354
// Upload layers
355355
stream::iter(layers)
356356
.map(|layer| {
357+
// This avoids moving `self` which is &mut Self
358+
// into the async block. We only want to capture
359+
// as &Self
357360
let this = &self;
358361
async move {
359362
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(())
379365
}
380366
})
381-
.boxed() // Workaround to rustc issue https://github.com/rust-lang/rust/issues/104382
382367
.buffer_unordered(self.config.max_concurrent_upload)
383368
.try_for_each(future::ok)
384369
.await?;
385370

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?;
400374
let manifest_url = self.push_manifest(image_ref, &manifest.into()).await?;
401375

402376
Ok(PushResponse {
@@ -405,6 +379,24 @@ impl Client {
405379
})
406380
}
407381

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+
408400
/// Pushes a blob to the registry as a monolith
409401
///
410402
/// Returns the pullable location of the blob
@@ -1002,6 +994,28 @@ impl Client {
1002994
))
1003995
}
1004996

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+
10051019
/// Pushes the manifest for a specified image
10061020
///
10071021
/// Returns pullable manifest URL
@@ -1018,12 +1032,30 @@ impl Client {
10181032
let mut ser = serde_json::Serializer::with_formatter(&mut body, CanonicalFormatter::new());
10191033
manifest.serialize(&mut ser).unwrap();
10201034

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+
10211054
// Calculate the digest of the manifest, this is useful
10221055
// if the remote registry is violating the OCI Distribution Specification.
10231056
// See below for more details.
10241057
let manifest_hash = sha256_digest(&body);
10251058

1026-
debug!(?url, ?content_type, "push manifest");
10271059
let res = RequestBuilderWrapper::from_client(self, |client| client.put(url.clone()))
10281060
.apply_auth(image, RegistryOperation::Push)?
10291061
.into_request_builder()
@@ -2383,6 +2415,46 @@ mod test {
23832415
assert_eq!(manifest.config.digest, pulled_manifest.config.digest);
23842416
}
23852417

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+
23862458
#[tokio::test]
23872459
async fn test_platform_resolution() {
23882460
// test that we get an error when we pull a manifest list

0 commit comments

Comments
 (0)