Skip to content
This repository was archived by the owner on Jan 15, 2025. It is now read-only.

Commit 1298981

Browse files
authored
Merge pull request #278 from cgwalters/importer-progress
container: Add support for layer fetch notifications
2 parents 9f712ad + 2d547fa commit 1298981

File tree

2 files changed

+102
-9
lines changed

2 files changed

+102
-9
lines changed

lib/src/cli.rs

+49-9
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ use std::convert::TryFrom;
1515
use std::ffi::OsString;
1616
use std::path::PathBuf;
1717
use structopt::StructOpt;
18+
use tokio::sync::mpsc::Receiver;
1819
use tokio_stream::StreamExt;
1920

2021
use crate::commit::container_commit;
2122
use crate::container as ostree_container;
23+
use crate::container::store::{ImportProgress, PreparedImport};
2224
use crate::container::{Config, ImageReference, OstreeImageReference, UnencapsulateOptions};
2325
use ostree_container::store::{ImageImporter, PrepareResult};
2426
use ostree_container::UnencapsulationProgress;
@@ -352,6 +354,46 @@ enum ProgressOrFinish {
352354
Finished(Result<ostree_container::Import>),
353355
}
354356

357+
/// Render an import progress notification as a string.
358+
pub fn layer_progress_format(p: &ImportProgress) -> String {
359+
let (starting, s, layer) = match p {
360+
ImportProgress::OstreeChunkStarted(v) => (true, "ostree chunk", v),
361+
ImportProgress::OstreeChunkCompleted(v) => (false, "ostree chunk", v),
362+
ImportProgress::DerivedLayerStarted(v) => (true, "layer", v),
363+
ImportProgress::DerivedLayerCompleted(v) => (false, "layer", v),
364+
};
365+
// podman outputs 12 characters of digest, let's add 7 for `sha256:`.
366+
let short_digest = layer.digest().chars().take(12 + 7).collect::<String>();
367+
if starting {
368+
let size = glib::format_size(layer.size() as u64);
369+
format!("Fetching {s} {short_digest} ({size})")
370+
} else {
371+
format!("Fetched {s} {short_digest}")
372+
}
373+
}
374+
375+
async fn handle_layer_progress_print(mut r: Receiver<ImportProgress>) {
376+
while let Some(v) = r.recv().await {
377+
println!("{}", layer_progress_format(&v));
378+
}
379+
}
380+
381+
fn print_layer_status(prep: &PreparedImport) {
382+
let (stored, to_fetch, to_fetch_size) =
383+
prep.all_layers()
384+
.fold((0u32, 0u32, 0u64), |(stored, to_fetch, sz), v| {
385+
if v.commit.is_some() {
386+
(stored + 1, to_fetch, sz)
387+
} else {
388+
(stored, to_fetch + 1, sz + v.size())
389+
}
390+
});
391+
if to_fetch > 0 {
392+
let size = crate::glib::format_size(to_fetch_size);
393+
println!("layers stored: {stored} needed: {to_fetch} ({size})");
394+
}
395+
}
396+
355397
/// Import a container image with an encapsulated ostree commit.
356398
async fn container_import(
357399
repo: &ostree::Repo,
@@ -455,22 +497,20 @@ async fn container_store(
455497
proxyopts: ContainerProxyOpts,
456498
) -> Result<()> {
457499
let mut imp = ImageImporter::new(repo, imgref, proxyopts.into()).await?;
500+
let layer_progress = imp.request_progress();
458501
let prep = match imp.prepare().await? {
459502
PrepareResult::AlreadyPresent(c) => {
460503
println!("No changes in {} => {}", imgref, c.merge_commit);
461504
return Ok(());
462505
}
463506
PrepareResult::Ready(r) => r,
464507
};
465-
for layer in prep.all_layers() {
466-
if layer.commit.is_some() {
467-
println!("Using layer: {}", layer.digest());
468-
} else {
469-
let size = crate::glib::format_size(layer.size());
470-
println!("Downloading layer: {} ({})", layer.digest(), size);
471-
}
472-
}
473-
let import = imp.import(prep).await?;
508+
print_layer_status(&prep);
509+
let progress_printer =
510+
tokio::task::spawn(async move { handle_layer_progress_print(layer_progress).await });
511+
let import = imp.import(prep).await;
512+
let _ = progress_printer.await;
513+
let import = import?;
474514
let commit = &repo.load_commit(&import.merge_commit)?.0;
475515
let commit_meta = &glib::VariantDict::new(Some(&commit.child_value(0)));
476516
let filtered = commit_meta.lookup::<ostree_container::store::MetaFilteredData>(

lib/src/container/store.rs

+53
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use ostree::{gio, glib};
1616
use std::collections::HashMap;
1717
use std::iter::FromIterator;
1818
use std::sync::{Arc, Mutex};
19+
use tokio::sync::mpsc::{Receiver, Sender};
1920

2021
/// Configuration for the proxy.
2122
///
@@ -54,6 +55,19 @@ fn ref_for_image(l: &ImageReference) -> Result<String> {
5455
refescape::prefix_escape_for_ref(IMAGE_PREFIX, &l.to_string())
5556
}
5657

58+
/// Sent across a channel to track start and end of a container fetch.
59+
#[derive(Debug)]
60+
pub enum ImportProgress {
61+
/// Started fetching this layer.
62+
OstreeChunkStarted(Descriptor),
63+
/// Successfully completed the fetch of this layer.
64+
OstreeChunkCompleted(Descriptor),
65+
/// Started fetching this layer.
66+
DerivedLayerStarted(Descriptor),
67+
/// Successfully completed the fetch of this layer.
68+
DerivedLayerCompleted(Descriptor),
69+
}
70+
5771
/// State of an already pulled layered image.
5872
#[derive(Debug, PartialEq, Eq)]
5973
pub struct LayeredImageState {
@@ -95,6 +109,8 @@ pub struct ImageImporter {
95109
imgref: OstreeImageReference,
96110
target_imgref: Option<OstreeImageReference>,
97111
pub(crate) proxy_img: OpenedImage,
112+
113+
layer_progress: Option<Sender<ImportProgress>>,
98114
}
99115

100116
/// Result of invoking [`LayeredImageImporter::prepare`].
@@ -274,6 +290,7 @@ impl ImageImporter {
274290
proxy_img,
275291
target_imgref: None,
276292
imgref: imgref.clone(),
293+
layer_progress: None,
277294
})
278295
}
279296

@@ -286,6 +303,14 @@ impl ImageImporter {
286303
self.prepare_internal(false).await
287304
}
288305

306+
/// Create a channel receiver that will get notifications for layer fetches.
307+
pub fn request_progress(&mut self) -> Receiver<ImportProgress> {
308+
assert!(self.layer_progress.is_none());
309+
let (s, r) = tokio::sync::mpsc::channel(2);
310+
self.layer_progress = Some(s);
311+
r
312+
}
313+
289314
/// Determine if there is a new manifest, and if so return its digest.
290315
#[context("Fetching manifest")]
291316
pub(crate) async fn prepare_internal(&mut self, verify_layers: bool) -> Result<PrepareResult> {
@@ -397,6 +422,10 @@ impl ImageImporter {
397422
if layer.commit.is_some() {
398423
continue;
399424
}
425+
if let Some(p) = self.layer_progress.as_ref() {
426+
p.send(ImportProgress::OstreeChunkStarted(layer.layer.clone()))
427+
.await?;
428+
}
400429
let (blob, driver) =
401430
fetch_layer_decompress(&mut self.proxy, &self.proxy_img, &layer.layer).await?;
402431
let blob = super::unencapsulate::ProgressReader {
@@ -425,8 +454,18 @@ impl ImageImporter {
425454
});
426455
let commit = super::unencapsulate::join_fetch(import_task, driver).await?;
427456
layer.commit = commit;
457+
if let Some(p) = self.layer_progress.as_ref() {
458+
p.send(ImportProgress::OstreeChunkCompleted(layer.layer.clone()))
459+
.await?;
460+
}
428461
}
429462
if import.ostree_commit_layer.commit.is_none() {
463+
if let Some(p) = self.layer_progress.as_ref() {
464+
p.send(ImportProgress::OstreeChunkStarted(
465+
import.ostree_commit_layer.layer.clone(),
466+
))
467+
.await?;
468+
}
430469
let (blob, driver) = fetch_layer_decompress(
431470
&mut self.proxy,
432471
&self.proxy_img,
@@ -457,6 +496,12 @@ impl ImageImporter {
457496
});
458497
let commit = super::unencapsulate::join_fetch(import_task, driver).await?;
459498
import.ostree_commit_layer.commit = Some(commit);
499+
if let Some(p) = self.layer_progress.as_ref() {
500+
p.send(ImportProgress::OstreeChunkCompleted(
501+
import.ostree_commit_layer.layer.clone(),
502+
))
503+
.await?;
504+
}
460505
};
461506
Ok(())
462507
}
@@ -503,6 +548,10 @@ impl ImageImporter {
503548
tracing::debug!("Reusing fetched commit {}", c);
504549
layer_commits.push(c.to_string());
505550
} else {
551+
if let Some(p) = self.layer_progress.as_ref() {
552+
p.send(ImportProgress::DerivedLayerStarted(layer.layer.clone()))
553+
.await?;
554+
}
506555
let (blob, driver) = super::unencapsulate::fetch_layer_decompress(
507556
&mut proxy,
508557
&self.proxy_img,
@@ -525,6 +574,10 @@ impl ImageImporter {
525574
let filtered = HashMap::from_iter(r.filtered.into_iter());
526575
layer_filtered_content.insert(layer.digest().to_string(), filtered);
527576
}
577+
if let Some(p) = self.layer_progress.as_ref() {
578+
p.send(ImportProgress::DerivedLayerCompleted(layer.layer.clone()))
579+
.await?;
580+
}
528581
}
529582
}
530583

0 commit comments

Comments
 (0)