@@ -14,7 +14,7 @@ use std::{iter::zip, sync::Arc};
1414
1515use anyhow:: { bail, Context , Result } ;
1616use async_compression:: tokio:: bufread:: { GzipDecoder , ZstdDecoder } ;
17- use containers_image_proxy:: { ImageProxy , ImageProxyConfig , OpenedImage } ;
17+ use containers_image_proxy:: { ConvertedLayerInfo , ImageProxy , ImageProxyConfig , OpenedImage } ;
1818use indicatif:: { MultiProgress , ProgressBar , ProgressStyle } ;
1919use oci_spec:: image:: { Descriptor , ImageConfiguration , ImageManifest , MediaType } ;
2020use rustix:: process:: geteuid;
@@ -31,11 +31,31 @@ use crate::{config_identifier, layer_identifier, tar::split_async, ContentAndVer
3131pub ( crate ) const TAR_LAYER_CONTENT_TYPE : u64 = u64:: from_le_bytes ( * b"ocilayer" ) ;
3232pub ( 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+
3453struct ImageOp < ObjectID : FsVerityHashValue > {
3554 repo : Arc < Repository < ObjectID > > ,
3655 proxy : ImageProxy ,
3756 img : OpenedImage ,
3857 progress : MultiProgress ,
58+ transport : Transport ,
3959}
4060
4161impl < 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