Skip to content

Commit 3614a62

Browse files
skopeo: Get uncompressed layer when pulling from containers-storage
When pulling from containers storage we were requesting a layer using the compressed sha digest for that particular layer which caused skopeo to throw the error ``` failed to invoke method GetBlob: locating item named "sha256:<snip>" for image with ID "<snip>" (consider removing the image to resolve the issue): file does not exist ``` To remedy this, when pulling from containers storage we now query using the `get_blob` method using the uncompressed sha digest for the layer Related: bootc-dev/bootc#1703 Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
1 parent 4a06016 commit 3614a62

1 file changed

Lines changed: 126 additions & 24 deletions

File tree

crates/composefs-oci/src/skopeo.rs

Lines changed: 126 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::{iter::zip, sync::Arc};
1414

1515
use anyhow::{bail, Context, Result};
1616
use async_compression::tokio::bufread::{GzipDecoder, ZstdDecoder};
17-
use containers_image_proxy::{ImageProxy, ImageProxyConfig, OpenedImage};
17+
use containers_image_proxy::{ConvertedLayerInfo, ImageProxy, ImageProxyConfig, OpenedImage};
1818
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
1919
use oci_spec::image::{Descriptor, ImageConfiguration, ImageManifest, MediaType};
2020
use rustix::process::geteuid;
@@ -31,11 +31,31 @@ use crate::{config_identifier, layer_identifier, tar::split_async, ContentAndVer
3131
pub(crate) const TAR_LAYER_CONTENT_TYPE: u64 = u64::from_le_bytes(*b"ocilayer");
3232
pub(crate) const OCI_CONFIG_CONTENT_TYPE: u64 = u64::from_le_bytes(*b"ociconfg");
3333

34+
/// A backend/transport for OCI/Docker images.
35+
#[derive(Debug)]
36+
pub enum Transport {
37+
/// A remote Docker/OCI registry (`registry:` or `docker://`)
38+
Registry,
39+
/// A local OCI directory (`oci:`)
40+
OciDir,
41+
/// A local OCI archive tarball (`oci-archive:`)
42+
OciArchive,
43+
/// A local Docker archive tarball (`docker-archive:`)
44+
DockerArchive,
45+
/// Local container storage (`containers-storage:`)
46+
ContainerStorage,
47+
/// Local directory (`dir:`)
48+
Dir,
49+
/// Local Docker daemon (`docker-daemon:`)
50+
DockerDaemon,
51+
}
52+
3453
struct ImageOp<ObjectID: FsVerityHashValue> {
3554
repo: Arc<Repository<ObjectID>>,
3655
proxy: ImageProxy,
3756
img: OpenedImage,
3857
progress: MultiProgress,
58+
transport: Transport,
3959
}
4060

4161
impl<ObjectID: FsVerityHashValue> ImageOp<ObjectID> {
@@ -44,6 +64,24 @@ impl<ObjectID: FsVerityHashValue> ImageOp<ObjectID> {
4464
imgref: &str,
4565
img_proxy_config: Option<ImageProxyConfig>,
4666
) -> Result<Self> {
67+
// Detect transport from image reference
68+
let transport = if imgref.starts_with("containers-storage:") {
69+
Transport::ContainerStorage
70+
} else if imgref.starts_with("oci:") {
71+
Transport::OciDir
72+
} else if imgref.starts_with("oci-archive:") {
73+
Transport::OciArchive
74+
} else if imgref.starts_with("docker-archive:") {
75+
Transport::DockerArchive
76+
} else if imgref.starts_with("dir:") {
77+
Transport::Dir
78+
} else if imgref.starts_with("docker-daemon:") {
79+
Transport::DockerDaemon
80+
} else {
81+
// Default to registry for docker:// or registry: prefixes, or bare image names
82+
Transport::Registry
83+
};
84+
4785
// See https://github.com/containers/skopeo/issues/2563
4886
let skopeo_cmd = if imgref.starts_with("containers-storage:") && !geteuid().is_root() {
4987
let mut cmd = Command::new("podman");
@@ -86,10 +124,17 @@ impl<ObjectID: FsVerityHashValue> ImageOp<ObjectID> {
86124
proxy,
87125
img,
88126
progress,
127+
transport,
89128
})
90129
}
91130

92-
pub async fn ensure_layer(&self, diff_id: &str, descriptor: &Descriptor) -> Result<ObjectID> {
131+
pub async fn ensure_layer(
132+
&self,
133+
diff_id: &str,
134+
descriptor: &Descriptor,
135+
uncompressed_layer_info: Arc<Option<Vec<ConvertedLayerInfo>>>,
136+
layer_idx: usize,
137+
) -> Result<ObjectID> {
93138
// We need to use the per_manifest descriptor to download the compressed layer but it gets
94139
// stored in the repository via the per_config descriptor. Our return value is the
95140
// fsverity digest for the corresponding splitstream.
@@ -101,10 +146,37 @@ impl<ObjectID: FsVerityHashValue> ImageOp<ObjectID> {
101146
Ok(layer_id)
102147
} else {
103148
// Otherwise, we need to fetch it...
104-
let (blob_reader, driver) = self.proxy.get_descriptor(&self.img, descriptor).await?;
149+
let ((blob_reader, driver), size) = match self.transport {
150+
Transport::ContainerStorage => {
151+
let layer = uncompressed_layer_info
152+
.as_ref()
153+
.as_ref()
154+
.ok_or_else(|| anyhow::anyhow!("Failed to get uncompressed layer info"))?;
155+
156+
let layer = layer.get(layer_idx).ok_or_else(|| {
157+
anyhow::anyhow!(
158+
"Failed to get uncompressed layer info for layer index {layer_idx}"
159+
)
160+
})?;
161+
162+
(
163+
self.proxy
164+
.get_blob(&self.img, &layer.digest, layer.size)
165+
.await?,
166+
layer.size,
167+
)
168+
}
169+
170+
_ => (
171+
self.proxy
172+
.get_blob(&self.img, descriptor.digest(), descriptor.size())
173+
.await?,
174+
descriptor.size(),
175+
),
176+
};
105177

106178
// See https://github.com/containers/containers-image-proxy-rs/issues/71
107-
let blob_reader = blob_reader.take(descriptor.size());
179+
let blob_reader = blob_reader.take(size);
108180

109181
let bar = self.progress.add(ProgressBar::new(descriptor.size()));
110182
bar.set_style(ProgressStyle::with_template("[eta {eta}] {bar:40.cyan/blue} {decimal_bytes:>7}/{decimal_total_bytes:7} {msg}")
@@ -113,32 +185,43 @@ impl<ObjectID: FsVerityHashValue> ImageOp<ObjectID> {
113185
let progress = bar.wrap_async_read(blob_reader);
114186
self.progress.println(format!("Fetching layer {diff_id}"))?;
115187

116-
let object_id = match descriptor.media_type() {
117-
MediaType::ImageLayer => {
188+
let object_id = match self.transport {
189+
Transport::ContainerStorage => {
118190
split_async(
119191
BufReader::new(progress),
120192
self.repo.clone(),
121193
TAR_LAYER_CONTENT_TYPE,
122194
)
123195
.await?
124196
}
125-
MediaType::ImageLayerGzip => {
126-
split_async(
127-
BufReader::new(GzipDecoder::new(BufReader::new(progress))),
128-
self.repo.clone(),
129-
TAR_LAYER_CONTENT_TYPE,
130-
)
131-
.await?
132-
}
133-
MediaType::ImageLayerZstd => {
134-
split_async(
135-
BufReader::new(ZstdDecoder::new(BufReader::new(progress))),
136-
self.repo.clone(),
137-
TAR_LAYER_CONTENT_TYPE,
138-
)
139-
.await?
140-
}
141-
other => bail!("Unsupported layer media type {other:?}"),
197+
198+
_ => match descriptor.media_type() {
199+
MediaType::ImageLayer => {
200+
split_async(
201+
BufReader::new(progress),
202+
self.repo.clone(),
203+
TAR_LAYER_CONTENT_TYPE,
204+
)
205+
.await?
206+
}
207+
MediaType::ImageLayerGzip => {
208+
split_async(
209+
BufReader::new(GzipDecoder::new(BufReader::new(progress))),
210+
self.repo.clone(),
211+
TAR_LAYER_CONTENT_TYPE,
212+
)
213+
.await?
214+
}
215+
MediaType::ImageLayerZstd => {
216+
split_async(
217+
BufReader::new(ZstdDecoder::new(BufReader::new(progress))),
218+
self.repo.clone(),
219+
TAR_LAYER_CONTENT_TYPE,
220+
)
221+
.await?
222+
}
223+
other => bail!("Unsupported layer media type {other:?}"),
224+
},
142225
};
143226

144227
// skopeo is doing data checksums for us to make sure the content we received is equal
@@ -194,14 +277,33 @@ impl<ObjectID: FsVerityHashValue> ImageOp<ObjectID> {
194277
let threads = available_parallelism()?;
195278
let sem = Arc::new(Semaphore::new(threads.into()));
196279
let mut entries = vec![];
280+
281+
let uncompressed_layer_info = match self.transport {
282+
Transport::ContainerStorage => self.proxy.get_layer_info(&self.img).await?,
283+
_ => None,
284+
};
285+
286+
let uncompressed_layer_info = Arc::new(uncompressed_layer_info);
287+
197288
for (mld, diff_id) in layers {
198289
let diff_id_ = diff_id.clone();
199290
let self_ = Arc::clone(self);
200291
let permit = Arc::clone(&sem).acquire_owned().await?;
201292
let descriptor = mld.clone();
293+
294+
// SAFETY: We get descriptor from the manifest_layers itself
295+
let layer_idx = manifest_layers
296+
.iter()
297+
.position(|d| *d == descriptor)
298+
.unwrap();
299+
300+
let uncompressed_layer_info = Arc::clone(&uncompressed_layer_info);
301+
202302
let future = tokio::spawn(async move {
203303
let _permit = permit;
204-
self_.ensure_layer(&diff_id_, &descriptor).await
304+
self_
305+
.ensure_layer(&diff_id_, &descriptor, uncompressed_layer_info, layer_idx)
306+
.await
205307
});
206308
entries.push((diff_id, future));
207309
}

0 commit comments

Comments
 (0)