Skip to content

Commit bc28c59

Browse files
authored
Merge pull request #695 from jeckersb/backend_v2
Prep for multiple container image stores
2 parents 1a171a6 + 54dfc34 commit bc28c59

File tree

8 files changed

+232
-53
lines changed

8 files changed

+232
-53
lines changed

lib/src/cli.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,12 @@ pub(crate) async fn get_locked_sysroot() -> Result<ostree_ext::sysroot::SysrootL
430430
Ok(sysroot)
431431
}
432432

433+
#[context("Initializing storage")]
434+
pub(crate) async fn get_storage() -> Result<crate::store::Storage> {
435+
let sysroot = get_locked_sysroot().await?;
436+
Ok(crate::store::Storage::new(sysroot))
437+
}
438+
433439
#[context("Querying root privilege")]
434440
pub(crate) fn require_root() -> Result<()> {
435441
let uid = rustix::process::getuid();
@@ -482,7 +488,7 @@ fn prepare_for_write() -> Result<()> {
482488
/// Implementation of the `bootc upgrade` CLI command.
483489
#[context("Upgrading")]
484490
async fn upgrade(opts: UpgradeOpts) -> Result<()> {
485-
let sysroot = &get_locked_sysroot().await?;
491+
let sysroot = &get_storage().await?;
486492
let repo = &sysroot.repo();
487493
let (booted_deployment, _deployments, host) =
488494
crate::status::get_status_require_booted(sysroot)?;
@@ -619,7 +625,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
619625

620626
let cancellable = gio::Cancellable::NONE;
621627

622-
let sysroot = &get_locked_sysroot().await?;
628+
let sysroot = &get_storage().await?;
623629
let repo = &sysroot.repo();
624630
let (booted_deployment, _deployments, host) =
625631
crate::status::get_status_require_booted(sysroot)?;
@@ -658,14 +664,14 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
658664
/// Implementation of the `bootc rollback` CLI command.
659665
#[context("Rollback")]
660666
async fn rollback(_opts: RollbackOpts) -> Result<()> {
661-
let sysroot = &get_locked_sysroot().await?;
667+
let sysroot = &get_storage().await?;
662668
crate::deploy::rollback(sysroot).await
663669
}
664670

665671
/// Implementation of the `bootc edit` CLI command.
666672
#[context("Editing spec")]
667673
async fn edit(opts: EditOpts) -> Result<()> {
668-
let sysroot = &get_locked_sysroot().await?;
674+
let sysroot = &get_storage().await?;
669675
let repo = &sysroot.repo();
670676

671677
let (booted_deployment, _deployments, host) =

lib/src/deploy.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use ostree_ext::sysroot::SysrootLock;
2222
use crate::spec::ImageReference;
2323
use crate::spec::{BootOrder, HostSpec};
2424
use crate::status::labels_of_config;
25+
use crate::store::Storage;
2526

2627
// TODO use https://github.com/ostreedev/ostree-rs-ext/pull/493/commits/afc1837ff383681b947de30c0cefc70080a4f87a
2728
const BASE_IMAGE_PREFIX: &str = "ostree/container/baseimage/bootc";
@@ -405,7 +406,7 @@ pub(crate) async fn stage(
405406
}
406407

407408
/// Implementation of rollback functionality
408-
pub(crate) async fn rollback(sysroot: &SysrootLock) -> Result<()> {
409+
pub(crate) async fn rollback(sysroot: &Storage) -> Result<()> {
409410
const ROLLBACK_JOURNAL_ID: &str = "26f3b1eb24464d12aa5e7b544a6b5468";
410411
let repo = &sysroot.repo();
411412
let (booted_deployment, deployments, host) = crate::status::get_status_require_booted(sysroot)?;

lib/src/image.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub(crate) async fn list_entrypoint() -> Result<()> {
2626
#[context("Pushing image")]
2727
pub(crate) async fn push_entrypoint(source: Option<&str>, target: Option<&str>) -> Result<()> {
2828
let transport = Transport::ContainerStorage;
29-
let sysroot = crate::cli::get_locked_sysroot().await?;
29+
let sysroot = crate::cli::get_storage().await?;
3030

3131
let repo = &sysroot.repo();
3232

lib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub(crate) mod metadata;
2626
mod reboot;
2727
mod reexec;
2828
mod status;
29+
mod store;
2930
mod task;
3031
mod utils;
3132

lib/src/spec.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ 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 container storage backend
48+
pub enum Store {
49+
/// Use the ostree-container storage backend.
50+
#[default]
51+
#[value(alias = "ostreecontainer")] // default is kebab-case
52+
OstreeContainer,
53+
}
54+
4355
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, JsonSchema)]
4456
#[serde(rename_all = "camelCase")]
4557
/// The host specification
@@ -112,6 +124,9 @@ pub struct BootEntry {
112124
pub incompatible: bool,
113125
/// Whether this entry will be subject to garbage collection
114126
pub pinned: bool,
127+
/// The container storage backend
128+
#[serde(default)]
129+
pub store: Option<Store>,
115130
/// If this boot entry is ostree based, the corresponding state
116131
pub ostree: Option<BootEntryOstree>,
117132
}
@@ -258,4 +273,14 @@ mod tests {
258273
assert_eq!(displayed.as_str(), src);
259274
assert_eq!(format!("{s:#}"), src);
260275
}
276+
277+
#[test]
278+
fn test_store_from_str() {
279+
use clap::ValueEnum;
280+
281+
// should be case-insensitive, kebab-case optional
282+
assert!(Store::from_str("Ostree-Container", true).is_ok());
283+
assert!(Store::from_str("OstrEeContAiner", true).is_ok());
284+
assert!(Store::from_str("invalid", true).is_err());
285+
}
261286
}

lib/src/status.rs

Lines changed: 27 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +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;
13-
use ostree_ext::sysroot::SysrootLock;
1412

1513
use crate::cli::OutputFormat;
16-
use crate::spec::{BootEntry, BootOrder, Host, HostSpec, HostStatus, HostType, ImageStatus};
14+
use crate::spec::{BootEntry, BootOrder, Host, HostSpec, HostStatus, HostType};
1715
use crate::spec::{ImageReference, ImageSignature};
16+
use crate::store::{CachedImageStatus, ContainerImageStore, Storage};
1817

1918
impl From<ostree_container::SignatureSource> for ImageSignature {
2019
fn from(sig: ostree_container::SignatureSource) -> Self {
@@ -115,65 +114,46 @@ pub(crate) fn labels_of_config(
115114
config.config().as_ref().and_then(|c| c.labels().as_ref())
116115
}
117116

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-
141117
/// Given an OSTree deployment, parse out metadata into our spec.
142118
#[context("Reading deployment metadata")]
143119
fn boot_entry_from_deployment(
144-
sysroot: &SysrootLock,
120+
sysroot: &Storage,
145121
deployment: &ostree::Deployment,
146122
) -> Result<BootEntry> {
147-
let repo = &sysroot.repo();
148-
let (image, cached_update, incompatible) = if let Some(origin) = deployment.origin().as_ref() {
123+
let (
124+
store,
125+
CachedImageStatus {
126+
image,
127+
cached_update,
128+
},
129+
incompatible,
130+
) = if let Some(origin) = deployment.origin().as_ref() {
149131
let incompatible = crate::utils::origin_has_rpmostree_stuff(origin);
150-
let (image, cached) = if incompatible {
132+
let (store, cached_imagestatus) = if incompatible {
151133
// If there are local changes, we can't represent it as a bootc compatible image.
152-
(None, None)
134+
(None, CachedImageStatus::default())
153135
} 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)
136+
let store = deployment.store()?;
137+
let store = store.as_ref().unwrap_or(&sysroot.store);
138+
let spec = Some(store.spec());
139+
let status = store.imagestatus(sysroot, deployment, image)?;
140+
141+
(spec, status)
164142
} else {
165143
// The deployment isn't using a container image
166-
(None, None)
144+
(None, CachedImageStatus::default())
167145
};
168-
(image, cached, incompatible)
146+
(store, cached_imagestatus, incompatible)
169147
} else {
170148
// The deployment has no origin at all (this generally shouldn't happen)
171-
(None, None, false)
149+
(None, CachedImageStatus::default(), false)
172150
};
151+
173152
let r = BootEntry {
174153
image,
175154
cached_update,
176155
incompatible,
156+
store,
177157
pinned: deployment.is_pinned(),
178158
ostree: Some(crate::spec::BootEntryOstree {
179159
checksum: deployment.csum().into(),
@@ -203,7 +183,7 @@ impl BootEntry {
203183

204184
/// A variant of [`get_status`] that requires a booted deployment.
205185
pub(crate) fn get_status_require_booted(
206-
sysroot: &SysrootLock,
186+
sysroot: &Storage,
207187
) -> Result<(ostree::Deployment, Deployments, Host)> {
208188
let booted_deployment = sysroot.require_booted_deployment()?;
209189
let (deployments, host) = get_status(sysroot, Some(&booted_deployment))?;
@@ -214,7 +194,7 @@ pub(crate) fn get_status_require_booted(
214194
/// a more native Rust structure.
215195
#[context("Computing status")]
216196
pub(crate) fn get_status(
217-
sysroot: &SysrootLock,
197+
sysroot: &Storage,
218198
booted_deployment: Option<&ostree::Deployment>,
219199
) -> Result<(Deployments, Host)> {
220200
let stateroot = booted_deployment.as_ref().map(|d| d.osname());
@@ -311,7 +291,7 @@ pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
311291
let host = if !Utf8Path::new("/run/ostree-booted").try_exists()? {
312292
Default::default()
313293
} else {
314-
let sysroot = super::cli::get_locked_sysroot().await?;
294+
let sysroot = super::cli::get_storage().await?;
315295
let booted_deployment = sysroot.booted_deployment();
316296
let (_deployments, host) = get_status(&sysroot, booted_deployment.as_ref())?;
317297
host

lib/src/store/mod.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use std::env;
2+
use std::ops::Deref;
3+
4+
use anyhow::Result;
5+
use clap::ValueEnum;
6+
7+
use ostree_ext::container::OstreeImageReference;
8+
use ostree_ext::keyfileext::KeyFileExt;
9+
use ostree_ext::ostree;
10+
use ostree_ext::sysroot::SysrootLock;
11+
12+
use crate::spec::ImageStatus;
13+
14+
mod ostree_container;
15+
16+
pub(crate) struct Storage {
17+
pub sysroot: SysrootLock,
18+
pub store: Box<dyn ContainerImageStoreImpl>,
19+
}
20+
21+
#[derive(Default)]
22+
pub(crate) struct CachedImageStatus {
23+
pub image: Option<ImageStatus>,
24+
pub cached_update: Option<ImageStatus>,
25+
}
26+
27+
pub(crate) trait ContainerImageStore {
28+
fn store(&self) -> Result<Option<Box<dyn ContainerImageStoreImpl>>>;
29+
}
30+
31+
pub(crate) trait ContainerImageStoreImpl {
32+
fn spec(&self) -> crate::spec::Store;
33+
34+
fn imagestatus(
35+
&self,
36+
sysroot: &SysrootLock,
37+
deployment: &ostree::Deployment,
38+
image: OstreeImageReference,
39+
) -> Result<CachedImageStatus>;
40+
}
41+
42+
impl Deref for Storage {
43+
type Target = SysrootLock;
44+
45+
fn deref(&self) -> &Self::Target {
46+
&self.sysroot
47+
}
48+
}
49+
50+
impl Storage {
51+
pub fn new(sysroot: SysrootLock) -> Self {
52+
let store = match env::var("BOOTC_STORAGE") {
53+
Ok(val) => crate::spec::Store::from_str(&val, true).unwrap_or_else(|_| {
54+
let default = crate::spec::Store::default();
55+
tracing::warn!("Unknown BOOTC_STORAGE option {val}, falling back to {default:?}");
56+
default
57+
}),
58+
Err(_) => crate::spec::Store::default(),
59+
};
60+
61+
let store = load(store);
62+
63+
Self { sysroot, store }
64+
}
65+
}
66+
67+
impl ContainerImageStore for ostree::Deployment {
68+
fn store<'a>(&self) -> Result<Option<Box<dyn ContainerImageStoreImpl>>> {
69+
if let Some(origin) = self.origin().as_ref() {
70+
if let Some(store) = origin.optional_string("bootc", "backend")? {
71+
let store =
72+
crate::spec::Store::from_str(&store, true).map_err(anyhow::Error::msg)?;
73+
Ok(Some(load(store)))
74+
} else {
75+
Ok(None)
76+
}
77+
} else {
78+
Ok(None)
79+
}
80+
}
81+
}
82+
83+
pub(crate) fn load(ty: crate::spec::Store) -> Box<dyn ContainerImageStoreImpl> {
84+
match ty {
85+
crate::spec::Store::OstreeContainer => Box::new(ostree_container::OstreeContainerStore),
86+
}
87+
}

0 commit comments

Comments
 (0)