From f7036d5b9b3ed66230c2f0e53d99a558b0775d8d Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 1 Dec 2023 15:26:43 -0500 Subject: [PATCH 1/3] Move pull code into deploy Prep for a podman backend; also we should thin out the logic in `cli.rs`. Signed-off-by: Colin Walters --- lib/src/cli.rs | 61 ++++------------------------------------------- lib/src/deploy.rs | 52 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 57 deletions(-) diff --git a/lib/src/cli.rs b/lib/src/cli.rs index 42af1bcba..c3cc7f928 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.repo(), 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:?}"); @@ -424,7 +371,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(repo, &target, opts.quiet).await?; if !opts.retain { // By default, we prune the previous ostree ref so it will go away after later upgrades @@ -467,7 +414,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(repo, 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..01936cd7d 100644 --- a/lib/src/deploy.rs +++ b/lib/src/deploy.rs @@ -9,6 +9,7 @@ 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; @@ -39,6 +40,57 @@ impl<'a> RequiredHostSpec<'a> { } } +/// 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( + 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) +} + pub(crate) async fn cleanup(sysroot: &SysrootLock) -> Result<()> { let repo = sysroot.repo(); let sysroot = sysroot.sysroot.clone(); From 695605488a521e3b2c12d3bab95dfbc4126705a8 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 1 Dec 2023 15:28:52 -0500 Subject: [PATCH 2/3] deploy: Change pull function to take sysroot Prep for a podman backend, where we will write outside of the ostree repo. Signed-off-by: Colin Walters --- lib/src/cli.rs | 7 +++---- lib/src/deploy.rs | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/cli.rs b/lib/src/cli.rs index c3cc7f928..28bc4087f 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -294,7 +294,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> { } } } else { - let fetched = crate::deploy::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:?}"); @@ -371,7 +371,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> { } let new_spec = RequiredHostSpec::from_spec(&new_spec)?; - let fetched = crate::deploy::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 @@ -395,7 +395,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 { @@ -414,7 +413,7 @@ async fn edit(opts: EditOpts) -> Result<()> { return Ok(()); } let new_spec = RequiredHostSpec::from_spec(&new_host.spec)?; - let fetched = crate::deploy::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 01936cd7d..fd6fa3f47 100644 --- a/lib/src/deploy.rs +++ b/lib/src/deploy.rs @@ -54,10 +54,11 @@ pub(crate) async fn new_importer( /// Wrapper for pulling a container image, wiring up status output. #[context("Pulling")] pub(crate) async fn pull( - repo: &ostree::Repo, + 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? { From fde6bddb5c06a67326ecb94cbd71b3eda0c84dca Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 2 Dec 2023 10:12:13 -0500 Subject: [PATCH 3/3] deploy: Pass around subset of ostree-container image state Prep for a podman backend. Signed-off-by: Colin Walters --- lib/src/cli.rs | 7 +++++-- lib/src/deploy.rs | 50 +++++++++++++++++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/lib/src/cli.rs b/lib/src/cli.rs index 28bc4087f..eaf724153 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -315,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(); + } } } } diff --git a/lib/src/deploy.rs b/lib/src/deploy.rs index fd6fa3f47..2ff8bebd5 100644 --- a/lib/src/deploy.rs +++ b/lib/src/deploy.rs @@ -6,7 +6,6 @@ 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; @@ -28,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. @@ -40,6 +46,29 @@ 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, @@ -57,14 +86,14 @@ pub(crate) async fn pull( sysroot: &SysrootLock, imgref: &ImageReference, quiet: bool, -) -> Result> { +) -> 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(c); + return Ok(Box::new((*c).into())); } PrepareResult::Ready(p) => p, }; @@ -89,7 +118,7 @@ pub(crate) async fn pull( { eprintln!("{msg}") } - Ok(import) + Ok(Box::new((*import).into())) } pub(crate) async fn cleanup(sysroot: &SysrootLock) -> Result<()> { @@ -143,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(), @@ -166,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)); @@ -187,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);