From 5fab4185f5f1665c35e982f9c87b542016d0210d Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sun, 15 Oct 2023 11:46:35 -0400 Subject: [PATCH] Add RequiredHostSpec We want to gracefully handle the case where there's no booted/staged image - i.e. when there's no spec. However we only support deploying a container image. So add a `RequiredHostSpec` that is like `HostSpec` but without the `Option`. This drops out a few unnecessary `Option`-juggling in the deploy code. Prep for adding configmaps to the spec. Signed-off-by: Colin Walters --- lib/src/cli.rs | 15 ++++++------- lib/src/deploy.rs | 55 ++++++++++++++++++++++++++++------------------- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/lib/src/cli.rs b/lib/src/cli.rs index f60d21220..156f0a20a 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -19,6 +19,7 @@ use std::io::Seek; use std::os::unix::process::CommandExt; use std::process::Command; +use crate::deploy::RequiredHostSpec; use crate::spec::Host; use crate::spec::ImageReference; @@ -290,6 +291,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> { "Booted deployment contains local rpm-ostree modifications; cannot upgrade via bootc" )); } + let spec = RequiredHostSpec::from_spec(&host.spec)?; let booted_image = status .booted .map(|b| b.query_image(repo)) @@ -334,7 +336,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> { println!("Staged update present, not changed."); } else { let osname = booted_deployment.osname(); - crate::deploy::stage(sysroot, &osname, &fetched, &host.spec).await?; + 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); @@ -386,6 +388,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> { if new_spec == host.spec { anyhow::bail!("No changes in current host spec"); } + let new_spec = RequiredHostSpec::from_spec(&new_spec)?; let fetched = pull(repo, &target, opts.quiet).await?; @@ -429,17 +432,13 @@ async fn edit(opts: EditOpts) -> Result<()> { if new_host.spec == host.spec { anyhow::bail!("No changes in current host spec"); } - let new_image = new_host - .spec - .image - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Unable to transition to unset image"))?; - let fetched = pull(repo, new_image, opts.quiet).await?; + let new_spec = RequiredHostSpec::from_spec(&new_host.spec)?; + let fetched = pull(repo, &new_spec.image, opts.quiet).await?; // TODO gc old layers here let stateroot = booted_deployment.osname(); - crate::deploy::stage(sysroot, &stateroot, &fetched, &new_host.spec).await?; + crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec).await?; Ok(()) } diff --git a/lib/src/deploy.rs b/lib/src/deploy.rs index d225613fc..45936a071 100644 --- a/lib/src/deploy.rs +++ b/lib/src/deploy.rs @@ -14,6 +14,7 @@ use ostree_ext::ostree::Deployment; use ostree_ext::sysroot::SysrootLock; use crate::spec::HostSpec; +use crate::spec::ImageReference; // TODO use https://github.com/ostreedev/ostree-rs-ext/pull/493/commits/afc1837ff383681b947de30c0cefc70080a4f87a const BASE_IMAGE_PREFIX: &str = "ostree/container/baseimage/bootc"; @@ -21,6 +22,23 @@ const BASE_IMAGE_PREFIX: &str = "ostree/container/baseimage/bootc"; /// Set on an ostree commit if this is a derived commit const BOOTC_DERIVED_KEY: &str = "bootc.derived"; +/// Variant of HostSpec but required to be filled out +pub(crate) struct RequiredHostSpec<'a> { + pub(crate) image: &'a ImageReference, +} + +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. + pub(crate) fn from_spec(spec: &'a HostSpec) -> Result { + let image = spec + .image + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Missing image in specification"))?; + Ok(Self { image }) + } +} + pub(crate) async fn cleanup(sysroot: &SysrootLock) -> Result<()> { let repo = sysroot.repo(); let sysroot = sysroot.sysroot.clone(); @@ -96,21 +114,16 @@ pub(crate) async fn stage( sysroot: &SysrootLock, stateroot: &str, image: &LayeredImageState, - spec: &HostSpec, + spec: &RequiredHostSpec<'_>, ) -> Result<()> { let merge_deployment = sysroot.merge_deployment(Some(stateroot)); let origin = glib::KeyFile::new(); - let ostree_imgref = spec - .image - .as_ref() - .map(|imgref| OstreeImageReference::from(imgref.clone())); - if let Some(imgref) = ostree_imgref.as_ref() { - origin.set_string( - "origin", - ostree_container::deploy::ORIGIN_CONTAINER, - imgref.to_string().as_str(), - ); - } + let imgref = OstreeImageReference::from(spec.image.clone()); + origin.set_string( + "origin", + ostree_container::deploy::ORIGIN_CONTAINER, + imgref.to_string().as_str(), + ); crate::deploy::deploy( sysroot, merge_deployment.as_ref(), @@ -120,17 +133,15 @@ pub(crate) async fn stage( ) .await?; crate::deploy::cleanup(sysroot).await?; - if let Some(imgref) = ostree_imgref.as_ref() { - println!("Queued for next boot: {imgref}"); - if let Some(version) = image - .configuration - .as_ref() - .and_then(ostree_container::version_for_config) - { - println!(" Version: {version}"); - } - println!(" Digest: {}", image.manifest_digest); + println!("Queued for next boot: {imgref}"); + if let Some(version) = image + .configuration + .as_ref() + .and_then(ostree_container::version_for_config) + { + println!(" Version: {version}"); } + println!(" Digest: {}", image.manifest_digest); ostree_container::deploy::remove_undeployed_images(sysroot).context("Pruning images")?; Ok(())