diff --git a/lib/src/cli.rs b/lib/src/cli.rs index 42af1bcba..eaf724153 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -7,9 +7,7 @@ use camino::Utf8PathBuf; use clap::Parser; use fn_error_context::context; use ostree::gio; -use ostree_container::store::LayeredImageState; use ostree_container::store::PrepareResult; -use ostree_container::OstreeImageReference; use ostree_ext::container as ostree_container; use ostree_ext::container::SignatureSource; use ostree_ext::keyfileext::KeyFileExt; @@ -214,57 +212,6 @@ pub(crate) async fn get_locked_sysroot() -> Result Result { - let config = Default::default(); - let mut imp = ostree_container::store::ImageImporter::new(repo, imgref, config).await?; - imp.require_bootable(); - Ok(imp) -} - -/// Wrapper for pulling a container image, wiring up status output. -#[context("Pulling")] -async fn pull( - repo: &ostree::Repo, - imgref: &ImageReference, - quiet: bool, -) -> Result> { - let imgref = &OstreeImageReference::from(imgref.clone()); - let mut imp = new_importer(repo, imgref).await?; - let prep = match imp.prepare().await? { - PrepareResult::AlreadyPresent(c) => { - println!("No changes in {} => {}", imgref, c.manifest_digest); - return Ok(c); - } - PrepareResult::Ready(p) => p, - }; - if let Some(warning) = prep.deprecated_warning() { - ostree_ext::cli::print_deprecated_warning(warning).await; - } - ostree_ext::cli::print_layer_status(&prep); - let printer = (!quiet).then(|| { - let layer_progress = imp.request_progress(); - let layer_byte_progress = imp.request_layer_progress(); - tokio::task::spawn(async move { - ostree_ext::cli::handle_layer_progress_print(layer_progress, layer_byte_progress).await - }) - }); - let import = imp.import(prep).await; - if let Some(printer) = printer { - let _ = printer.await; - } - let import = import?; - if let Some(msg) = - ostree_container::store::image_filtered_content_warning(repo, &imgref.imgref)? - { - eprintln!("{msg}") - } - Ok(import) -} - #[context("Querying root privilege")] pub(crate) fn require_root() -> Result<()> { let uid = rustix::process::getuid(); @@ -327,7 +274,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> { let mut changed = false; if opts.check { let imgref = imgref.clone().into(); - let mut imp = new_importer(repo, &imgref).await?; + let mut imp = crate::deploy::new_importer(repo, &imgref).await?; match imp.prepare().await? { PrepareResult::AlreadyPresent(_) => { println!("No changes in: {}", imgref); @@ -347,7 +294,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> { } } } else { - let fetched = pull(&sysroot.repo(), imgref, opts.quiet).await?; + let fetched = crate::deploy::pull(&sysroot, imgref, opts.quiet).await?; let staged_digest = staged_image.as_ref().map(|s| s.image_digest.as_str()); let fetched_digest = fetched.manifest_digest.as_str(); tracing::debug!("staged: {staged_digest:?}"); @@ -368,8 +315,11 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> { crate::deploy::stage(sysroot, &osname, &fetched, &spec).await?; changed = true; if let Some(prev) = booted_image.as_ref() { - let diff = ostree_container::ManifestDiff::new(&prev.manifest, &fetched.manifest); - diff.print(); + if let Some(fetched_manifest) = fetched.get_manifest(repo)? { + let diff = + ostree_container::ManifestDiff::new(&prev.manifest, &fetched_manifest); + diff.print(); + } } } } @@ -424,7 +374,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> { } let new_spec = RequiredHostSpec::from_spec(&new_spec)?; - let fetched = pull(repo, &target, opts.quiet).await?; + let fetched = crate::deploy::pull(sysroot, &target, opts.quiet).await?; if !opts.retain { // By default, we prune the previous ostree ref so it will go away after later upgrades @@ -448,7 +398,6 @@ async fn switch(opts: SwitchOpts) -> Result<()> { async fn edit(opts: EditOpts) -> Result<()> { prepare_for_write().await?; let sysroot = &get_locked_sysroot().await?; - let repo = &sysroot.repo(); let (booted_deployment, _deployments, host) = crate::status::get_status_require_booted(sysroot)?; let new_host: Host = if let Some(filename) = opts.filename { @@ -467,7 +416,7 @@ async fn edit(opts: EditOpts) -> Result<()> { return Ok(()); } let new_spec = RequiredHostSpec::from_spec(&new_host.spec)?; - let fetched = pull(repo, new_spec.image, opts.quiet).await?; + let fetched = crate::deploy::pull(sysroot, new_spec.image, opts.quiet).await?; // TODO gc old layers here diff --git a/lib/src/deploy.rs b/lib/src/deploy.rs index 87b6a9bb0..2ff8bebd5 100644 --- a/lib/src/deploy.rs +++ b/lib/src/deploy.rs @@ -6,9 +6,9 @@ use anyhow::{Context, Result}; use fn_error_context::context; use ostree::{gio, glib}; -use ostree_container::store::LayeredImageState; use ostree_container::OstreeImageReference; use ostree_ext::container as ostree_container; +use ostree_ext::container::store::PrepareResult; use ostree_ext::ostree; use ostree_ext::ostree::Deployment; use ostree_ext::sysroot::SysrootLock; @@ -27,6 +27,13 @@ pub(crate) struct RequiredHostSpec<'a> { pub(crate) image: &'a ImageReference, } +/// State of a locally fetched image +pub(crate) struct ImageState { + pub(crate) manifest_digest: String, + pub(crate) version: Option, + pub(crate) ostree_commit: String, +} + impl<'a> RequiredHostSpec<'a> { /// Given a (borrowed) host specification, "unwrap" its internal /// options, giving a spec that is required to have a base container image. @@ -39,6 +46,81 @@ impl<'a> RequiredHostSpec<'a> { } } +impl From for ImageState { + fn from(value: ostree_container::store::LayeredImageState) -> Self { + let version = value.version().map(|v| v.to_owned()); + let ostree_commit = value.get_commit().to_owned(); + Self { + manifest_digest: value.manifest_digest, + version, + ostree_commit, + } + } +} + +impl ImageState { + /// Fetch the manifest corresponding to this image. May not be available in all backends. + pub(crate) fn get_manifest( + &self, + repo: &ostree::Repo, + ) -> Result> { + ostree_container::store::query_image_commit(repo, &self.ostree_commit) + .map(|v| Some(v.manifest)) + } +} + +/// Wrapper for pulling a container image, wiring up status output. +pub(crate) async fn new_importer( + repo: &ostree::Repo, + imgref: &ostree_container::OstreeImageReference, +) -> Result { + let config = Default::default(); + let mut imp = ostree_container::store::ImageImporter::new(repo, imgref, config).await?; + imp.require_bootable(); + Ok(imp) +} + +/// Wrapper for pulling a container image, wiring up status output. +#[context("Pulling")] +pub(crate) async fn pull( + sysroot: &SysrootLock, + imgref: &ImageReference, + quiet: bool, +) -> Result> { + let repo = &sysroot.repo(); + let imgref = &OstreeImageReference::from(imgref.clone()); + let mut imp = new_importer(repo, imgref).await?; + let prep = match imp.prepare().await? { + PrepareResult::AlreadyPresent(c) => { + println!("No changes in {} => {}", imgref, c.manifest_digest); + return Ok(Box::new((*c).into())); + } + PrepareResult::Ready(p) => p, + }; + if let Some(warning) = prep.deprecated_warning() { + ostree_ext::cli::print_deprecated_warning(warning).await; + } + ostree_ext::cli::print_layer_status(&prep); + let printer = (!quiet).then(|| { + let layer_progress = imp.request_progress(); + let layer_byte_progress = imp.request_layer_progress(); + tokio::task::spawn(async move { + ostree_ext::cli::handle_layer_progress_print(layer_progress, layer_byte_progress).await + }) + }); + let import = imp.import(prep).await; + if let Some(printer) = printer { + let _ = printer.await; + } + let import = import?; + if let Some(msg) = + ostree_container::store::image_filtered_content_warning(repo, &imgref.imgref)? + { + eprintln!("{msg}") + } + Ok(Box::new((*import).into())) +} + pub(crate) async fn cleanup(sysroot: &SysrootLock) -> Result<()> { let repo = sysroot.repo(); let sysroot = sysroot.sysroot.clone(); @@ -90,16 +172,15 @@ async fn deploy( sysroot: &SysrootLock, merge_deployment: Option<&Deployment>, stateroot: &str, - image: &LayeredImageState, + image: &ImageState, origin: &glib::KeyFile, ) -> Result<()> { let stateroot = Some(stateroot); // Copy to move into thread - let base_commit = image.get_commit().to_owned(); let cancellable = gio::Cancellable::NONE; let _new_deployment = sysroot.stage_tree_with_options( stateroot, - &base_commit, + image.ostree_commit.as_str(), Some(origin), merge_deployment, &Default::default(), @@ -113,7 +194,7 @@ async fn deploy( pub(crate) async fn stage( sysroot: &SysrootLock, stateroot: &str, - image: &LayeredImageState, + image: &ImageState, spec: &RequiredHostSpec<'_>, ) -> Result<()> { let merge_deployment = sysroot.merge_deployment(Some(stateroot)); @@ -134,11 +215,7 @@ pub(crate) async fn stage( .await?; crate::deploy::cleanup(sysroot).await?; println!("Queued for next boot: {imgref}"); - if let Some(version) = image - .configuration - .as_ref() - .and_then(ostree_container::version_for_config) - { + if let Some(version) = image.version.as_deref() { println!(" Version: {version}"); } println!(" Digest: {}", image.manifest_digest);