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