Skip to content

Commit 0c57786

Browse files
committed
WIP prep for multiple backends
1 parent 1fb8a15 commit 0c57786

File tree

5 files changed

+168
-42
lines changed

5 files changed

+168
-42
lines changed

lib/src/backend/mod.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use anyhow::{anyhow, Result};
2+
use clap::ValueEnum;
3+
4+
use ostree::glib;
5+
use ostree_ext::container::OstreeImageReference;
6+
use ostree_ext::keyfileext::KeyFileExt;
7+
use ostree_ext::ostree;
8+
use ostree_ext::sysroot::SysrootLock;
9+
10+
use crate::spec::ImageStatus;
11+
12+
mod ostree_container;
13+
14+
#[derive(Default)]
15+
pub(crate) struct CachedImageStatus {
16+
pub image: Option<ImageStatus>,
17+
pub cached_update: Option<ImageStatus>,
18+
}
19+
20+
pub(crate) trait Backend {
21+
fn backend(&self) -> Result<Box<dyn BackendImpl>>;
22+
}
23+
24+
pub(crate) trait BackendImpl {
25+
fn imagestatus(
26+
&self,
27+
sysroot: &SysrootLock,
28+
deployment: &ostree::Deployment,
29+
image: OstreeImageReference,
30+
) -> Result<CachedImageStatus>;
31+
}
32+
33+
impl Backend for crate::spec::Backend {
34+
fn backend<'a>(&self) -> Result<Box<dyn BackendImpl>> {
35+
match self {
36+
crate::spec::Backend::OstreeContainer => {
37+
Ok(Box::new(ostree_container::OstreeContainerBackend))
38+
}
39+
}
40+
}
41+
}
42+
43+
impl Backend for ostree::Deployment {
44+
fn backend<'a>(&self) -> Result<Box<dyn BackendImpl>> {
45+
if let Some(origin) = self.origin().as_ref() {
46+
origin.backend()
47+
} else {
48+
Err(anyhow!("Deployment has no origin"))
49+
}
50+
}
51+
}
52+
53+
impl Backend for &glib::KeyFile {
54+
fn backend(&self) -> Result<Box<dyn BackendImpl>> {
55+
let backend = self
56+
.optional_string("bootc", "backend")?
57+
.map(|v| crate::spec::Backend::from_str(&v, true))
58+
.transpose()
59+
.map_err(anyhow::Error::msg)?
60+
.unwrap_or_default();
61+
backend.backend()
62+
}
63+
}

lib/src/backend/ostree_container.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use anyhow::{Context, Result};
2+
3+
use ostree_ext::container as ostree_container;
4+
use ostree_ext::oci_spec;
5+
use ostree_ext::oci_spec::image::ImageConfiguration;
6+
use ostree_ext::ostree;
7+
use ostree_ext::sysroot::SysrootLock;
8+
9+
use super::CachedImageStatus;
10+
use crate::spec::{ImageReference, ImageStatus};
11+
12+
pub(super) struct OstreeContainerBackend;
13+
14+
impl super::BackendImpl for OstreeContainerBackend {
15+
fn imagestatus(
16+
&self,
17+
sysroot: &SysrootLock,
18+
deployment: &ostree::Deployment,
19+
image: ostree_container::OstreeImageReference,
20+
) -> Result<CachedImageStatus> {
21+
let repo = &sysroot.repo();
22+
let image = ImageReference::from(image);
23+
let csum = deployment.csum();
24+
let imgstate = ostree_container::store::query_image_commit(repo, &csum)?;
25+
let cached = imgstate.cached_update.map(|cached| {
26+
create_imagestatus(image.clone(), &cached.manifest_digest, &cached.config)
27+
});
28+
let imagestatus =
29+
create_imagestatus(image, &imgstate.manifest_digest, &imgstate.configuration);
30+
31+
Ok(CachedImageStatus {
32+
image: Some(imagestatus),
33+
cached_update: cached,
34+
})
35+
}
36+
}
37+
38+
/// Convert between a subset of ostree-ext metadata and the exposed spec API.
39+
fn create_imagestatus(
40+
image: ImageReference,
41+
manifest_digest: &str,
42+
config: &ImageConfiguration,
43+
) -> ImageStatus {
44+
let labels = labels_of_config(config);
45+
let timestamp = labels
46+
.and_then(|l| {
47+
l.get(oci_spec::image::ANNOTATION_CREATED)
48+
.map(|s| s.as_str())
49+
})
50+
.and_then(try_deserialize_timestamp);
51+
52+
let version = ostree_container::version_for_config(config).map(ToOwned::to_owned);
53+
ImageStatus {
54+
image,
55+
version,
56+
timestamp,
57+
image_digest: manifest_digest.to_owned(),
58+
}
59+
}
60+
61+
fn labels_of_config(
62+
config: &oci_spec::image::ImageConfiguration,
63+
) -> Option<&std::collections::HashMap<String, String>> {
64+
config.config().as_ref().and_then(|c| c.labels().as_ref())
65+
}
66+
67+
fn try_deserialize_timestamp(t: &str) -> Option<chrono::DateTime<chrono::Utc>> {
68+
match chrono::DateTime::parse_from_rfc3339(t).context("Parsing timestamp") {
69+
Ok(t) => Some(t.into()),
70+
Err(e) => {
71+
tracing::warn!("Invalid timestamp in image: {:#}", e);
72+
None
73+
}
74+
}
75+
}

lib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#![allow(clippy::needless_borrow)]
1414
#![allow(clippy::needless_borrows_for_generic_args)]
1515

16+
pub(crate) mod backend;
1617
mod boundimage;
1718
pub mod cli;
1819
pub(crate) mod deploy;

lib/src/spec.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@ pub enum BootOrder {
4040
Rollback,
4141
}
4242

43+
#[derive(
44+
clap::ValueEnum, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema, Default,
45+
)]
46+
#[serde(rename_all = "camelCase")]
47+
/// The storage backend
48+
pub enum Backend {
49+
/// Use the ostree-container storage backend.
50+
#[default]
51+
OstreeContainer,
52+
}
53+
4354
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
4455
#[serde(rename_all = "camelCase")]
4556
/// The host specification

lib/src/status.rs

Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ use ostree_container::OstreeImageReference;
88
use ostree_ext::container as ostree_container;
99
use ostree_ext::keyfileext::KeyFileExt;
1010
use ostree_ext::oci_spec;
11-
use ostree_ext::oci_spec::image::ImageConfiguration;
1211
use ostree_ext::ostree;
1312
use ostree_ext::sysroot::SysrootLock;
1413

14+
use crate::backend::{Backend, CachedImageStatus};
1515
use crate::cli::OutputFormat;
16-
use crate::spec::{BootEntry, BootOrder, Host, HostSpec, HostStatus, HostType, ImageStatus};
16+
use crate::spec::{BootEntry, BootOrder, Host, HostSpec, HostStatus, HostType};
1717
use crate::spec::{ImageReference, ImageSignature};
1818

1919
impl From<ostree_container::SignatureSource> for ImageSignature {
@@ -115,61 +115,37 @@ pub(crate) fn labels_of_config(
115115
config.config().as_ref().and_then(|c| c.labels().as_ref())
116116
}
117117

118-
/// Convert between a subset of ostree-ext metadata and the exposed spec API.
119-
pub(crate) fn create_imagestatus(
120-
image: ImageReference,
121-
manifest_digest: &str,
122-
config: &ImageConfiguration,
123-
) -> ImageStatus {
124-
let labels = labels_of_config(config);
125-
let timestamp = labels
126-
.and_then(|l| {
127-
l.get(oci_spec::image::ANNOTATION_CREATED)
128-
.map(|s| s.as_str())
129-
})
130-
.and_then(try_deserialize_timestamp);
131-
132-
let version = ostree_container::version_for_config(config).map(ToOwned::to_owned);
133-
ImageStatus {
134-
image,
135-
version,
136-
timestamp,
137-
image_digest: manifest_digest.to_owned(),
138-
}
139-
}
140-
141118
/// Given an OSTree deployment, parse out metadata into our spec.
142119
#[context("Reading deployment metadata")]
143120
fn boot_entry_from_deployment(
144121
sysroot: &SysrootLock,
145122
deployment: &ostree::Deployment,
146123
) -> Result<BootEntry> {
147-
let repo = &sysroot.repo();
148-
let (image, cached_update, incompatible) = if let Some(origin) = deployment.origin().as_ref() {
124+
let empty = CachedImageStatus::default();
125+
let (
126+
CachedImageStatus {
127+
image,
128+
cached_update,
129+
},
130+
incompatible,
131+
) = if let Some(origin) = deployment.origin().as_ref() {
149132
let incompatible = crate::utils::origin_has_rpmostree_stuff(origin);
150-
let (image, cached) = if incompatible {
133+
let cached_imagestatus = if incompatible {
151134
// If there are local changes, we can't represent it as a bootc compatible image.
152-
(None, None)
135+
empty
153136
} else if let Some(image) = get_image_origin(origin)? {
154-
let image = ImageReference::from(image);
155-
let csum = deployment.csum();
156-
let imgstate = ostree_container::store::query_image_commit(repo, &csum)?;
157-
let cached = imgstate.cached_update.map(|cached| {
158-
create_imagestatus(image.clone(), &cached.manifest_digest, &cached.config)
159-
});
160-
let imagestatus =
161-
create_imagestatus(image, &imgstate.manifest_digest, &imgstate.configuration);
162-
// We found a container-image based deployment
163-
(Some(imagestatus), cached)
137+
let backend = deployment.backend()?;
138+
backend.imagestatus(sysroot, deployment, image)?
164139
} else {
165140
// The deployment isn't using a container image
166-
(None, None)
141+
empty
167142
};
168-
(image, cached, incompatible)
143+
(cached_imagestatus, incompatible)
169144
} else {
170145
// The deployment has no origin at all (this generally shouldn't happen)
171-
(None, None, false)
146+
(empty, false)
172147
};
148+
173149
let r = BootEntry {
174150
image,
175151
cached_update,

0 commit comments

Comments
 (0)