@@ -45,6 +45,16 @@ pub(crate) struct ImageState {
45
45
pub ( crate ) ostree_commit : String ,
46
46
}
47
47
48
+ /// Download information
49
+ #[ derive( Debug , serde:: Serialize ) ]
50
+ pub struct JsonProgress {
51
+ pub done_bytes : u64 ,
52
+ pub download_bytes : u64 ,
53
+ pub image_bytes : u64 ,
54
+ pub n_layers : usize ,
55
+ pub n_layers_done : usize ,
56
+ }
57
+
48
58
impl < ' a > RequiredHostSpec < ' a > {
49
59
/// Given a (borrowed) host specification, "unwrap" its internal
50
60
/// options, giving a spec that is required to have a base container image.
@@ -234,13 +244,73 @@ async fn handle_layer_progress_print(
234
244
}
235
245
}
236
246
247
+ /// Write container fetch progress to standard output.
248
+ async fn handle_layer_progress_print_jsonl (
249
+ mut layers : tokio:: sync:: mpsc:: Receiver < ostree_container:: store:: ImportProgress > ,
250
+ mut layer_bytes : tokio:: sync:: watch:: Receiver < Option < ostree_container:: store:: LayerProgress > > ,
251
+ n_layers_to_fetch : usize ,
252
+ download_bytes : u64 ,
253
+ image_bytes : u64 ,
254
+ ) {
255
+ let mut total_read = 0u64 ;
256
+ let mut layers_done: usize = 0 ;
257
+ let mut last_json_written = std:: time:: Instant :: now ( ) ;
258
+ loop {
259
+ tokio:: select! {
260
+ // Always handle layer changes first.
261
+ biased;
262
+ layer = layers. recv( ) => {
263
+ if let Some ( l) = layer {
264
+ if !l. is_starting( ) {
265
+ let layer = descriptor_of_progress( & l) ;
266
+ layers_done += 1 ;
267
+ total_read += total_read. saturating_add( layer. size( ) ) ;
268
+ }
269
+ } else {
270
+ // If the receiver is disconnected, then we're done
271
+ break
272
+ } ;
273
+ } ,
274
+ r = layer_bytes. changed( ) => {
275
+ if r. is_err( ) {
276
+ // If the receiver is disconnected, then we're done
277
+ break
278
+ }
279
+ let bytes = layer_bytes. borrow( ) ;
280
+ if let Some ( bytes) = & * bytes {
281
+ let done_bytes = total_read + bytes. fetched;
282
+
283
+ // Lets update the json output only on bytes fetched
284
+ // They are common enough, anyhow. Debounce on time.
285
+ let curr = std:: time:: Instant :: now( ) ;
286
+ if curr. duration_since( last_json_written) . as_secs_f64( ) > 0.2 {
287
+ let json = JsonProgress {
288
+ done_bytes,
289
+ download_bytes,
290
+ image_bytes,
291
+ n_layers: n_layers_to_fetch,
292
+ n_layers_done: layers_done,
293
+ } ;
294
+ let json = serde_json:: to_string( & json) . unwrap( ) ;
295
+ // Write to stderr so that consumer can filter this
296
+ eprintln!( "{}" , json) ;
297
+ last_json_written = curr;
298
+ }
299
+ }
300
+ }
301
+ }
302
+ }
303
+ }
304
+
305
+
237
306
/// Wrapper for pulling a container image, wiring up status output.
238
307
#[ context( "Pulling" ) ]
239
308
pub ( crate ) async fn pull (
240
309
repo : & ostree:: Repo ,
241
310
imgref : & ImageReference ,
242
311
target_imgref : Option < & OstreeImageReference > ,
243
312
quiet : bool ,
313
+ json : bool ,
244
314
) -> Result < Box < ImageState > > {
245
315
let ostree_imgref = & OstreeImageReference :: from ( imgref. clone ( ) ) ;
246
316
let mut imp = new_importer ( repo, ostree_imgref) . await ?;
@@ -262,14 +332,22 @@ pub(crate) async fn pull(
262
332
let layers_to_fetch = prep. layers_to_fetch ( ) . collect :: < Result < Vec < _ > > > ( ) ?;
263
333
let n_layers_to_fetch = layers_to_fetch. len ( ) ;
264
334
let download_bytes: u64 = layers_to_fetch. iter ( ) . map ( |( l, _) | l. layer . size ( ) ) . sum ( ) ;
335
+ let image_bytes: u64 = prep. all_layers ( ) . map ( |l| l. layer . size ( ) ) . sum ( ) ;
265
336
266
- let printer = ( !quiet) . then ( || {
337
+ let printer = ( !quiet || json ) . then ( || {
267
338
let layer_progress = imp. request_progress ( ) ;
268
339
let layer_byte_progress = imp. request_layer_progress ( ) ;
269
- tokio:: task:: spawn ( async move {
270
- handle_layer_progress_print ( layer_progress, layer_byte_progress, n_layers_to_fetch, download_bytes)
271
- . await
272
- } )
340
+ if json {
341
+ tokio:: task:: spawn ( async move {
342
+ handle_layer_progress_print_jsonl ( layer_progress, layer_byte_progress, n_layers_to_fetch, download_bytes, image_bytes)
343
+ . await
344
+ } )
345
+ } else {
346
+ tokio:: task:: spawn ( async move {
347
+ handle_layer_progress_print ( layer_progress, layer_byte_progress, n_layers_to_fetch, download_bytes)
348
+ . await
349
+ } )
350
+ }
273
351
} ) ;
274
352
let import = imp. import ( prep) . await ;
275
353
if let Some ( printer) = printer {
0 commit comments