Skip to content

Commit

Permalink
WIP prep for multiple backends
Browse files Browse the repository at this point in the history
  • Loading branch information
jeckersb committed Jul 18, 2024
1 parent 1fb8a15 commit 0c57786
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 42 deletions.
63 changes: 63 additions & 0 deletions lib/src/backend/mod.rs
Original file line number Diff line number Diff line change
@@ -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<ImageStatus>,
pub cached_update: Option<ImageStatus>,
}

pub(crate) trait Backend {
fn backend(&self) -> Result<Box<dyn BackendImpl>>;
}

pub(crate) trait BackendImpl {
fn imagestatus(
&self,
sysroot: &SysrootLock,
deployment: &ostree::Deployment,
image: OstreeImageReference,
) -> Result<CachedImageStatus>;
}

impl Backend for crate::spec::Backend {
fn backend<'a>(&self) -> Result<Box<dyn BackendImpl>> {
match self {
crate::spec::Backend::OstreeContainer => {
Ok(Box::new(ostree_container::OstreeContainerBackend))
}
}
}
}

impl Backend for ostree::Deployment {
fn backend<'a>(&self) -> Result<Box<dyn BackendImpl>> {
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<Box<dyn BackendImpl>> {
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()
}
}
75 changes: 75 additions & 0 deletions lib/src/backend/ostree_container.rs
Original file line number Diff line number Diff line change
@@ -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<CachedImageStatus> {
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<String, String>> {
config.config().as_ref().and_then(|c| c.labels().as_ref())
}

fn try_deserialize_timestamp(t: &str) -> Option<chrono::DateTime<chrono::Utc>> {
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
}
}
}
1 change: 1 addition & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions lib/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 18 additions & 42 deletions lib/src/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ostree_container::SignatureSource> for ImageSignature {
Expand Down Expand Up @@ -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<BootEntry> {
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,
Expand Down

0 comments on commit 0c57786

Please sign in to comment.