From 0c5778680b9a6a03769167233f606dbb3221be14 Mon Sep 17 00:00:00 2001 From: John Eckersberg Date: Tue, 16 Jul 2024 16:23:38 -0400 Subject: [PATCH] WIP prep for multiple backends --- lib/src/backend/mod.rs | 63 ++++++++++++++++++++++++ lib/src/backend/ostree_container.rs | 75 +++++++++++++++++++++++++++++ lib/src/lib.rs | 1 + lib/src/spec.rs | 11 +++++ lib/src/status.rs | 60 +++++++---------------- 5 files changed, 168 insertions(+), 42 deletions(-) create mode 100644 lib/src/backend/mod.rs create mode 100644 lib/src/backend/ostree_container.rs diff --git a/lib/src/backend/mod.rs b/lib/src/backend/mod.rs new file mode 100644 index 000000000..59c325948 --- /dev/null +++ b/lib/src/backend/mod.rs @@ -0,0 +1,63 @@ +use anyhow::{anyhow, Result}; +use clap::ValueEnum; + +use ostree::glib; +use ostree_ext::container::OstreeImageReference; +use ostree_ext::keyfileext::KeyFileExt; +use ostree_ext::ostree; +use ostree_ext::sysroot::SysrootLock; + +use crate::spec::ImageStatus; + +mod ostree_container; + +#[derive(Default)] +pub(crate) struct CachedImageStatus { + pub image: Option, + pub cached_update: Option, +} + +pub(crate) trait Backend { + fn backend(&self) -> Result>; +} + +pub(crate) trait BackendImpl { + fn imagestatus( + &self, + sysroot: &SysrootLock, + deployment: &ostree::Deployment, + image: OstreeImageReference, + ) -> Result; +} + +impl Backend for crate::spec::Backend { + fn backend<'a>(&self) -> Result> { + match self { + crate::spec::Backend::OstreeContainer => { + Ok(Box::new(ostree_container::OstreeContainerBackend)) + } + } + } +} + +impl Backend for ostree::Deployment { + fn backend<'a>(&self) -> Result> { + if let Some(origin) = self.origin().as_ref() { + origin.backend() + } else { + Err(anyhow!("Deployment has no origin")) + } + } +} + +impl Backend for &glib::KeyFile { + fn backend(&self) -> Result> { + let backend = self + .optional_string("bootc", "backend")? + .map(|v| crate::spec::Backend::from_str(&v, true)) + .transpose() + .map_err(anyhow::Error::msg)? + .unwrap_or_default(); + backend.backend() + } +} diff --git a/lib/src/backend/ostree_container.rs b/lib/src/backend/ostree_container.rs new file mode 100644 index 000000000..f3a41abc3 --- /dev/null +++ b/lib/src/backend/ostree_container.rs @@ -0,0 +1,75 @@ +use anyhow::{Context, Result}; + +use ostree_ext::container as ostree_container; +use ostree_ext::oci_spec; +use ostree_ext::oci_spec::image::ImageConfiguration; +use ostree_ext::ostree; +use ostree_ext::sysroot::SysrootLock; + +use super::CachedImageStatus; +use crate::spec::{ImageReference, ImageStatus}; + +pub(super) struct OstreeContainerBackend; + +impl super::BackendImpl for OstreeContainerBackend { + fn imagestatus( + &self, + sysroot: &SysrootLock, + deployment: &ostree::Deployment, + image: ostree_container::OstreeImageReference, + ) -> Result { + let repo = &sysroot.repo(); + let image = ImageReference::from(image); + let csum = deployment.csum(); + let imgstate = ostree_container::store::query_image_commit(repo, &csum)?; + let cached = imgstate.cached_update.map(|cached| { + create_imagestatus(image.clone(), &cached.manifest_digest, &cached.config) + }); + let imagestatus = + create_imagestatus(image, &imgstate.manifest_digest, &imgstate.configuration); + + Ok(CachedImageStatus { + image: Some(imagestatus), + cached_update: cached, + }) + } +} + +/// Convert between a subset of ostree-ext metadata and the exposed spec API. +fn create_imagestatus( + image: ImageReference, + manifest_digest: &str, + config: &ImageConfiguration, +) -> ImageStatus { + let labels = labels_of_config(config); + let timestamp = labels + .and_then(|l| { + l.get(oci_spec::image::ANNOTATION_CREATED) + .map(|s| s.as_str()) + }) + .and_then(try_deserialize_timestamp); + + let version = ostree_container::version_for_config(config).map(ToOwned::to_owned); + ImageStatus { + image, + version, + timestamp, + image_digest: manifest_digest.to_owned(), + } +} + +fn labels_of_config( + config: &oci_spec::image::ImageConfiguration, +) -> Option<&std::collections::HashMap> { + config.config().as_ref().and_then(|c| c.labels().as_ref()) +} + +fn try_deserialize_timestamp(t: &str) -> Option> { + match chrono::DateTime::parse_from_rfc3339(t).context("Parsing timestamp") { + Ok(t) => Some(t.into()), + Err(e) => { + tracing::warn!("Invalid timestamp in image: {:#}", e); + None + } + } +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index ed3a82d73..18aacb524 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -13,6 +13,7 @@ #![allow(clippy::needless_borrow)] #![allow(clippy::needless_borrows_for_generic_args)] +pub(crate) mod backend; mod boundimage; pub mod cli; pub(crate) mod deploy; diff --git a/lib/src/spec.rs b/lib/src/spec.rs index 5f6df9324..960c42946 100644 --- a/lib/src/spec.rs +++ b/lib/src/spec.rs @@ -40,6 +40,17 @@ pub enum BootOrder { Rollback, } +#[derive( + clap::ValueEnum, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema, Default, +)] +#[serde(rename_all = "camelCase")] +/// The storage backend +pub enum Backend { + /// Use the ostree-container storage backend. + #[default] + OstreeContainer, +} + #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] /// The host specification diff --git a/lib/src/status.rs b/lib/src/status.rs index 65386ff38..6f07ed102 100644 --- a/lib/src/status.rs +++ b/lib/src/status.rs @@ -8,12 +8,12 @@ use ostree_container::OstreeImageReference; use ostree_ext::container as ostree_container; use ostree_ext::keyfileext::KeyFileExt; use ostree_ext::oci_spec; -use ostree_ext::oci_spec::image::ImageConfiguration; use ostree_ext::ostree; use ostree_ext::sysroot::SysrootLock; +use crate::backend::{Backend, CachedImageStatus}; use crate::cli::OutputFormat; -use crate::spec::{BootEntry, BootOrder, Host, HostSpec, HostStatus, HostType, ImageStatus}; +use crate::spec::{BootEntry, BootOrder, Host, HostSpec, HostStatus, HostType}; use crate::spec::{ImageReference, ImageSignature}; impl From for ImageSignature { @@ -115,61 +115,37 @@ pub(crate) fn labels_of_config( config.config().as_ref().and_then(|c| c.labels().as_ref()) } -/// Convert between a subset of ostree-ext metadata and the exposed spec API. -pub(crate) fn create_imagestatus( - image: ImageReference, - manifest_digest: &str, - config: &ImageConfiguration, -) -> ImageStatus { - let labels = labels_of_config(config); - let timestamp = labels - .and_then(|l| { - l.get(oci_spec::image::ANNOTATION_CREATED) - .map(|s| s.as_str()) - }) - .and_then(try_deserialize_timestamp); - - let version = ostree_container::version_for_config(config).map(ToOwned::to_owned); - ImageStatus { - image, - version, - timestamp, - image_digest: manifest_digest.to_owned(), - } -} - /// Given an OSTree deployment, parse out metadata into our spec. #[context("Reading deployment metadata")] fn boot_entry_from_deployment( sysroot: &SysrootLock, deployment: &ostree::Deployment, ) -> Result { - let repo = &sysroot.repo(); - let (image, cached_update, incompatible) = if let Some(origin) = deployment.origin().as_ref() { + let empty = CachedImageStatus::default(); + let ( + CachedImageStatus { + image, + cached_update, + }, + incompatible, + ) = if let Some(origin) = deployment.origin().as_ref() { let incompatible = crate::utils::origin_has_rpmostree_stuff(origin); - let (image, cached) = if incompatible { + let cached_imagestatus = if incompatible { // If there are local changes, we can't represent it as a bootc compatible image. - (None, None) + empty } else if let Some(image) = get_image_origin(origin)? { - let image = ImageReference::from(image); - let csum = deployment.csum(); - let imgstate = ostree_container::store::query_image_commit(repo, &csum)?; - let cached = imgstate.cached_update.map(|cached| { - create_imagestatus(image.clone(), &cached.manifest_digest, &cached.config) - }); - let imagestatus = - create_imagestatus(image, &imgstate.manifest_digest, &imgstate.configuration); - // We found a container-image based deployment - (Some(imagestatus), cached) + let backend = deployment.backend()?; + backend.imagestatus(sysroot, deployment, image)? } else { // The deployment isn't using a container image - (None, None) + empty }; - (image, cached, incompatible) + (cached_imagestatus, incompatible) } else { // The deployment has no origin at all (this generally shouldn't happen) - (None, None, false) + (empty, false) }; + let r = BootEntry { image, cached_update,