Skip to content

Commit ab9ba9a

Browse files
committed
Pull alternative urls
This helps with pulling this image mcr.microsoft.com/windows/nanoserver/insider:10.0.20348.1 Signed-off-by: Sean Young <[email protected]>
1 parent 2de8286 commit ab9ba9a

File tree

1 file changed

+86
-21
lines changed

1 file changed

+86
-21
lines changed

src/client.rs

Lines changed: 86 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use crate::config::ConfigFile;
77
use crate::errors::*;
88
use crate::manifest::{
9-
ImageIndexEntry, OciImageIndex, OciImageManifest, OciManifest, Versioned,
9+
ImageIndexEntry, OciDescriptor, OciImageIndex, OciImageManifest, OciManifest, Versioned,
1010
IMAGE_CONFIG_MEDIA_TYPE, IMAGE_LAYER_GZIP_MEDIA_TYPE, IMAGE_LAYER_MEDIA_TYPE,
1111
IMAGE_MANIFEST_LIST_MEDIA_TYPE, IMAGE_MANIFEST_MEDIA_TYPE, OCI_IMAGE_INDEX_MEDIA_TYPE,
1212
OCI_IMAGE_MEDIA_TYPE,
@@ -361,7 +361,7 @@ impl Client {
361361
async move {
362362
let mut out: Vec<u8> = Vec::new();
363363
debug!("Pulling image layer");
364-
this.pull_blob(image, &layer.digest, &mut out).await?;
364+
this.pull_blob(image, layer, &mut out).await?;
365365
Ok::<_, OciDistributionError>(ImageLayer::new(
366366
out,
367367
layer.media_type.clone(),
@@ -840,8 +840,7 @@ impl Client {
840840

841841
let mut out: Vec<u8> = Vec::new();
842842
debug!("Pulling config layer");
843-
self.pull_blob(image, &manifest.config.digest, &mut out)
844-
.await?;
843+
self.pull_blob(image, &manifest.config, &mut out).await?;
845844
let media_type = manifest.config.media_type.clone();
846845
let annotations = manifest.annotations.clone();
847846
Ok((manifest, digest, Config::new(out, media_type, annotations)))
@@ -864,26 +863,50 @@ impl Client {
864863
/// Pull a single layer from an OCI registry.
865864
///
866865
/// This pulls the layer for a particular image that is identified by
867-
/// the given digest. The image reference is used to find the
866+
/// the given layer descriptor. The image reference is used to find the
868867
/// repository and the registry, but it is not used to verify that
869868
/// the digest is a layer inside of the image. (The manifest is
870869
/// used for that.)
871870
pub async fn pull_blob<T: AsyncWrite + Unpin>(
872871
&self,
873872
image: &Reference,
874-
digest: &str,
873+
layer: &OciDescriptor,
875874
mut out: T,
876875
) -> Result<()> {
877-
let url = self.to_v2_blob_url(image.resolve_registry(), image.repository(), digest);
878-
let mut stream = RequestBuilderWrapper::from_client(self, |client| client.get(&url))
876+
let url = self.to_v2_blob_url(image.resolve_registry(), image.repository(), &layer.digest);
877+
878+
let mut response = RequestBuilderWrapper::from_client(self, |client| client.get(&url))
879879
.apply_accept(MIME_TYPES_DISTRIBUTION_MANIFEST)?
880880
.apply_auth(image, RegistryOperation::Pull)
881881
.await?
882882
.into_request_builder()
883883
.send()
884-
.await?
885-
.error_for_status()?
886-
.bytes_stream();
884+
.await?;
885+
886+
if let Some(urls) = &layer.urls {
887+
for url in urls {
888+
if response.error_for_status_ref().is_ok() {
889+
break;
890+
}
891+
892+
let url = Url::parse(url)
893+
.map_err(|e| OciDistributionError::UrlParseError(e.to_string()))?;
894+
895+
if url.scheme() == "http" || url.scheme() == "https" {
896+
// NOTE: we must not authenticate on additional URLs as those
897+
// can be abused to leak credentials or tokens. Please
898+
// refer to CVE-2020-15157 for more information.
899+
response =
900+
RequestBuilderWrapper::from_client(self, |client| client.get(url.clone()))
901+
.apply_accept(MIME_TYPES_DISTRIBUTION_MANIFEST)?
902+
.into_request_builder()
903+
.send()
904+
.await?
905+
}
906+
}
907+
}
908+
909+
let mut stream = response.error_for_status()?.bytes_stream();
887910

888911
while let Some(bytes) = stream.next().await {
889912
out.write_all(&bytes?).await?;
@@ -899,16 +922,43 @@ impl Client {
899922
pub async fn pull_blob_stream(
900923
&self,
901924
image: &Reference,
902-
digest: &str,
925+
layer: &OciDescriptor,
903926
) -> Result<impl Stream<Item = std::result::Result<bytes::Bytes, std::io::Error>>> {
904-
let url = self.to_v2_blob_url(image.resolve_registry(), image.repository(), digest);
905-
let stream = RequestBuilderWrapper::from_client(self, |client| client.get(&url))
927+
let url = self.to_v2_blob_url(image.resolve_registry(), image.repository(), &layer.digest);
928+
929+
let mut response = RequestBuilderWrapper::from_client(self, |client| client.get(&url))
906930
.apply_accept(MIME_TYPES_DISTRIBUTION_MANIFEST)?
907931
.apply_auth(image, RegistryOperation::Pull)
908932
.await?
909933
.into_request_builder()
910934
.send()
911-
.await?
935+
.await?;
936+
937+
if let Some(urls) = &layer.urls {
938+
for url in urls {
939+
if response.error_for_status_ref().is_ok() {
940+
break;
941+
}
942+
943+
let url = Url::parse(url)
944+
.map_err(|e| OciDistributionError::UrlParseError(e.to_string()))?;
945+
946+
if url.scheme() == "http" || url.scheme() == "https" {
947+
// NOTE: we must not authenticate on additional URLs as those
948+
// can be abused to leak credentials or tokens. Please
949+
// refer to CVE-2020-15157 for more information.
950+
response =
951+
RequestBuilderWrapper::from_client(self, |client| client.get(url.clone()))
952+
.apply_accept(MIME_TYPES_DISTRIBUTION_MANIFEST)?
953+
.into_request_builder()
954+
.send()
955+
.await?
956+
}
957+
}
958+
}
959+
960+
let stream = response
961+
.error_for_status()?
912962
.bytes_stream()
913963
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e));
914964

@@ -1496,6 +1546,18 @@ pub fn linux_amd64_resolver(manifests: &[ImageIndexEntry]) -> Option<String> {
14961546
.map(|entry| entry.digest.clone())
14971547
}
14981548

1549+
/// A platform resolver that chooses the first windows/amd64 variant, if present
1550+
pub fn windows_amd64_resolver(manifests: &[ImageIndexEntry]) -> Option<String> {
1551+
manifests
1552+
.iter()
1553+
.find(|entry| {
1554+
entry.platform.as_ref().map_or(false, |platform| {
1555+
platform.os == "windows" && platform.architecture == "amd64"
1556+
})
1557+
})
1558+
.map(|entry| entry.digest.clone())
1559+
}
1560+
14991561
const MACOS: &str = "macos";
15001562
const DARWIN: &str = "darwin";
15011563

@@ -2259,7 +2321,7 @@ mod test {
22592321
// This call likes to flake, so we try it at least 5 times
22602322
let mut last_error = None;
22612323
for i in 1..6 {
2262-
if let Err(e) = c.pull_blob(&reference, &layer0.digest, &mut file).await {
2324+
if let Err(e) = c.pull_blob(&reference, &layer0, &mut file).await {
22632325
println!(
22642326
"Got error on pull_blob call attempt {}. Will retry in 1s: {:?}",
22652327
i, e
@@ -2304,7 +2366,7 @@ mod test {
23042366
let layer0 = &manifest.layers[0];
23052367

23062368
let layer_stream = c
2307-
.pull_blob_stream(&reference, &layer0.digest)
2369+
.pull_blob_stream(&reference, &layer0)
23082370
.await
23092371
.expect("failed to pull blob stream");
23102372

@@ -2615,22 +2677,25 @@ mod test {
26152677
.parse()
26162678
.unwrap();
26172679
let layer_data = vec![1u8, 2, 3, 4];
2618-
let layer_digest = sha256_digest(&layer_data);
2619-
c.push_blob(&layer_reference, &[1, 2, 3, 4], &layer_digest)
2680+
let layer = OciDescriptor {
2681+
digest: sha256_digest(&layer_data),
2682+
..Default::default()
2683+
};
2684+
c.push_blob(&layer_reference, &[1, 2, 3, 4], &layer.digest)
26202685
.await
26212686
.expect("Failed to push");
26222687

26232688
// Mount the layer at `image-repository`
26242689
let image_reference: Reference = format!("localhost:{}/image-repository", port)
26252690
.parse()
26262691
.unwrap();
2627-
c.mount_blob(&image_reference, &layer_reference, &layer_digest)
2692+
c.mount_blob(&image_reference, &layer_reference, &layer.digest)
26282693
.await
26292694
.expect("Failed to mount");
26302695

26312696
// Pull the layer from `image-repository`
26322697
let mut buf = Vec::new();
2633-
c.pull_blob(&image_reference, &layer_digest, &mut buf)
2698+
c.pull_blob(&image_reference, &layer, &mut buf)
26342699
.await
26352700
.expect("Failed to pull");
26362701

0 commit comments

Comments
 (0)