@@ -39,7 +39,7 @@ const DEFAULT_IMAGE_NAMESPACE: &str = "library";
39
39
const DEFAULT_IMAGE_TAG : & str = "latest" ;
40
40
41
41
const DOCKER_LAYERS_CACHE_PATH : & str = "/vagga/cache/docker-layers" ;
42
- const DOCKER_LAYERS_DOWNLOAD_CONCURRENCY : usize = 4 ;
42
+ const DOCKER_LAYERS_DOWNLOAD_CONCURRENCY : usize = 2 ;
43
43
44
44
#[ derive( Serialize , Deserialize , Debug ) ]
45
45
pub struct DockerImage {
@@ -125,29 +125,26 @@ impl BuildStep for DockerImage {
125
125
126
126
#[ cfg( feature="containers" ) ]
127
127
fn build ( & self , guard : & mut Guard , _build : bool ) -> Result < ( ) , StepError > {
128
- let insecure = self . insecure . unwrap_or_else ( || {
128
+ let insecure = self . insecure . unwrap_or_else ( ||
129
129
is_insecure_registry ( & self . registry , & guard. ctx . settings . docker_insecure_registries )
130
- } ) ;
130
+ ) ;
131
131
if !insecure {
132
132
capsule:: ensure ( & mut guard. ctx . capsule , & [ capsule:: Https ] ) ?;
133
133
}
134
134
Dir :: new ( DOCKER_LAYERS_CACHE_PATH )
135
135
. recursive ( true )
136
136
. create ( )
137
- . expect ( "Docker layers cache dir" ) ;
138
- let layer_paths = tokio:: runtime:: Builder :: new_current_thread ( )
137
+ . map_err ( |e|
138
+ format ! ( "Cannot create docker layers cache directory: {}" , e)
139
+ ) ?;
140
+ let dst_path = Path :: new ( "/vagga/root" ) . join ( & self . path . strip_prefix ( "/" ) . unwrap ( ) ) ;
141
+ tokio:: runtime:: Builder :: new_current_thread ( )
139
142
. enable_all ( )
140
143
. build ( )
141
- . expect ( "Tokio runtime" )
142
- . block_on ( download_image ( & self . registry , insecure, & self . image , & self . tag ) )
143
- . expect ( "Downloaded layers" ) ;
144
- let dst_path = Path :: new ( "/vagga/root" ) . join ( & self . path . strip_prefix ( "/" ) . unwrap ( ) ) ;
145
- for layer_path in layer_paths. iter ( ) {
146
- TarCmd :: new ( layer_path, & dst_path)
147
- . preserve_owner ( true )
148
- . entry_handler ( whiteout_entry_handler)
149
- . unpack ( ) ?;
150
- }
144
+ . map_err ( |e| format ! ( "Error creating tokio runtime: {}" , e) ) ?
145
+ . block_on ( download_and_unpack_image (
146
+ & self . registry , insecure, & self . image , & self . tag , & dst_path
147
+ ) ) ?;
151
148
Ok ( ( ) )
152
149
}
153
150
@@ -156,6 +153,16 @@ impl BuildStep for DockerImage {
156
153
}
157
154
}
158
155
156
+ fn is_insecure_registry (
157
+ registry : & str , insecure_registries : & HashSet < String >
158
+ ) -> bool {
159
+ let registry_host = match registry. split_once ( ':' ) {
160
+ Some ( ( host, _port) ) => host,
161
+ None => registry,
162
+ } ;
163
+ insecure_registries. contains ( registry_host)
164
+ }
165
+
159
166
/// See:
160
167
/// - https://github.com/moby/moby/blob/v20.10.11/pkg/archive/whiteouts.go
161
168
/// - https://github.com/moby/moby/blob/v20.10.11/pkg/archive/diff.go#L131
@@ -195,16 +202,10 @@ fn whiteout_entry_handler(entry: &Entry<Box<dyn Read>>, dst_path: &Path) -> Resu
195
202
Ok ( false )
196
203
}
197
204
198
- fn is_insecure_registry ( registry : & str , insecure_registries : & HashSet < String > ) -> bool {
199
- let registry_url = url:: Url :: parse ( & format ! ( "http://{}" , registry) ) . unwrap ( ) ;
200
- let registry_host = registry_url. domain ( ) . unwrap ( ) ;
201
- insecure_registries. contains ( registry_host)
202
- }
203
-
204
205
#[ cfg( feature="containers" ) ]
205
- async fn download_image (
206
- registry : & str , insecure : bool , image : & str , tag : & str
207
- ) -> Result < Vec < PathBuf > , StepError > {
206
+ async fn download_and_unpack_image (
207
+ registry : & str , insecure : bool , image : & str , tag : & str , dst_path : & Path
208
+ ) -> Result < ( ) , StepError > {
208
209
let auth_scope = format ! ( "repository:{}:pull" , image) ;
209
210
let client = build_client ( registry, insecure, & [ & auth_scope] ) . await ?;
210
211
@@ -216,22 +217,55 @@ async fn download_image(
216
217
let layers_download_semaphore = Arc :: new (
217
218
Semaphore :: new ( DOCKER_LAYERS_DOWNLOAD_CONCURRENCY )
218
219
) ;
219
- let layers_futures = layers_digests. iter ( )
220
- . map ( |digest| {
221
- let image = image. to_string ( ) ;
222
- let digest = digest. clone ( ) ;
223
- let client = client. clone ( ) ;
224
- let sem = layers_download_semaphore. clone ( ) ;
225
- tokio:: spawn ( async move {
226
- if let Ok ( _guard) = sem. acquire ( ) . await {
227
- info ! ( "Downloading docker layer: {}" , & digest) ;
228
- download_blob ( & client, & image, & digest) . await
229
- } else {
230
- panic ! ( "Semaphore was closed unexpectedly" )
220
+
221
+ use tokio:: sync:: oneshot;
222
+
223
+ let mut layers_futures = vec ! ( ) ;
224
+ let mut unpack_channels = vec ! ( ) ;
225
+ for digest in & layers_digests {
226
+ let image = image. to_string ( ) ;
227
+ let digest = digest. clone ( ) ;
228
+ let client = client. clone ( ) ;
229
+ let sem = layers_download_semaphore. clone ( ) ;
230
+ let ( tx, rx) = oneshot:: channel ( ) ;
231
+ unpack_channels. push ( rx) ;
232
+ let download_future = tokio:: spawn ( async move {
233
+ if let Ok ( _guard) = sem. acquire ( ) . await {
234
+ println ! ( "Downloading docker layer: {}" , & digest) ;
235
+ match download_blob ( & client, & image, & digest) . await {
236
+ Ok ( layer_path) => {
237
+ if let Err ( _) = tx. send ( ( digest. clone ( ) , layer_path) ) {
238
+ return Err ( format ! ( "Error sending downloaded layer" ) ) ;
239
+ }
240
+ Ok ( ( ) )
241
+ }
242
+ Err ( e) => Err ( e)
243
+ }
244
+ } else {
245
+ panic ! ( "Semaphore was closed unexpectedly" )
246
+ }
247
+ } ) ;
248
+ layers_futures. push ( download_future) ;
249
+ }
250
+
251
+ let dst_path = dst_path. to_path_buf ( ) ;
252
+ let unpack_future = tokio:: spawn ( async move {
253
+ for ch in unpack_channels {
254
+ match ch. await {
255
+ Ok ( ( digest, layer_path) ) => {
256
+ let dst_path = dst_path. clone ( ) ;
257
+ if let Err ( e) = unpack_layer ( digest, layer_path, dst_path) . await {
258
+ return Err ( e) ;
259
+ }
231
260
}
232
- } )
233
- } )
234
- . collect :: < Vec < _ > > ( ) ;
261
+ Err ( e) => return Err (
262
+ format ! ( "Error waiting downloaded layer: {}" , e)
263
+ ) ,
264
+ }
265
+ }
266
+ Ok ( ( ) )
267
+ } ) ;
268
+
235
269
let mut layers_paths = vec ! ( ) ;
236
270
let mut layers_errors = vec ! ( ) ;
237
271
for layer_res in futures:: future:: join_all ( layers_futures) . await . into_iter ( ) {
@@ -241,13 +275,32 @@ async fn download_image(
241
275
Err ( join_err) => layers_errors. push ( format ! ( "{}" , join_err) ) ,
242
276
}
243
277
}
278
+
279
+ unpack_future. await
280
+ . map_err ( |e| format ! ( "Error waiting unpack future: {}" , e) ) ??;
281
+
244
282
if !layers_errors. is_empty ( ) {
245
283
Err ( layers_errors. into ( ) )
246
284
} else {
247
- Ok ( layers_paths )
285
+ Ok ( ( ) )
248
286
}
249
287
}
250
288
289
+ async fn unpack_layer (
290
+ digest : String , layer_path : PathBuf , dst_path : PathBuf
291
+ ) -> Result < ( ) , String > {
292
+ let unpack_future_res = tokio:: task:: spawn_blocking ( move || {
293
+ println ! ( "Unpacking docker layer: {}" , digest) ;
294
+ TarCmd :: new ( & layer_path, & dst_path)
295
+ . preserve_owner ( true )
296
+ . entry_handler ( whiteout_entry_handler)
297
+ . unpack ( )
298
+ } ) . await ;
299
+ unpack_future_res
300
+ . map_err ( |e| format ! ( "Error waiting a unpack layer future: {}" , e) ) ?
301
+ . map_err ( |e| format ! ( "Error unpacking docker layer: {}" , e) )
302
+ }
303
+
251
304
#[ cfg( feature="containers" ) ]
252
305
async fn build_client (
253
306
registry : & str , insecure : bool , auth_scopes : & [ & str ]
@@ -271,7 +324,9 @@ async fn build_client(
271
324
async fn download_blob (
272
325
client : & RegistryClient , image : & str , layer_digest : & str
273
326
) -> Result < PathBuf , String > {
274
- let digest = layer_digest. split_once ( ':' ) . unwrap ( ) . 1 ;
327
+ let digest = layer_digest. split_once ( ':' )
328
+ . ok_or ( format ! ( "Invalid layer digest: {}" , layer_digest) ) ?
329
+ . 1 ;
275
330
let short_digest = & digest[ ..12 ] ;
276
331
277
332
let layers_cache = Path :: new ( DOCKER_LAYERS_CACHE_PATH ) ;
@@ -298,11 +353,11 @@ async fn download_blob(
298
353
299
354
println ! ( "Downloading docker blob: {}" , & short_digest) ;
300
355
let mut blob_stream = client. get_blob_stream ( image, layer_digest) . await
301
- . expect ( "Get blob response" ) ;
356
+ . map_err ( |e| format ! ( "Error getting docker blob response: {}" , e ) ) ? ;
302
357
let mut blob_file = tokio:: fs:: File :: create ( & blob_tmp_path) . await
303
- . expect ( "Create layer file" ) ;
358
+ . map_err ( |e| format ! ( "Cannot create layer file: {}" , e ) ) ? ;
304
359
while let Some ( chunk) = blob_stream. next ( ) . await {
305
- let chunk = chunk. expect ( "Layer chunk" ) ;
360
+ let chunk = chunk. map_err ( |e| format ! ( "Error fetching layer chunk: {}" , e ) ) ? ;
306
361
blob_file. write_all ( & chunk) . await
307
362
. map_err ( |e| format ! ( "Cannot write blob file: {}" , e) ) ?;
308
363
}
0 commit comments