Skip to content

Commit e0045a6

Browse files
committed
Factor out staging/deployment module
Prep for configmap support.
1 parent 221e382 commit e0045a6

File tree

3 files changed

+137
-46
lines changed

3 files changed

+137
-46
lines changed

lib/src/cli.rs

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,20 @@ use anyhow::{Context, Result};
66
use camino::Utf8PathBuf;
77
use clap::Parser;
88
use fn_error_context::context;
9-
use ostree::{gio, glib};
9+
use ostree::gio;
1010
use ostree_container::store::LayeredImageState;
1111
use ostree_container::store::PrepareResult;
1212
use ostree_container::OstreeImageReference;
1313
use ostree_ext::container as ostree_container;
1414
use ostree_ext::container::SignatureSource;
1515
use ostree_ext::keyfileext::KeyFileExt;
1616
use ostree_ext::ostree;
17-
use ostree_ext::sysroot::SysrootLock;
1817
use std::ffi::OsString;
1918
use std::io::Seek;
2019
use std::os::unix::process::CommandExt;
2120
use std::process::Command;
2221

2322
use crate::spec::Host;
24-
use crate::spec::HostSpec;
2523
use crate::spec::ImageReference;
2624

2725
/// Perform an upgrade operation
@@ -229,45 +227,6 @@ async fn pull(
229227
Ok(import)
230228
}
231229

232-
/// Stage (queue deployment of) a fetched container image.
233-
#[context("Staging")]
234-
async fn stage(
235-
sysroot: &SysrootLock,
236-
stateroot: &str,
237-
image: Box<LayeredImageState>,
238-
spec: &HostSpec,
239-
) -> Result<()> {
240-
let cancellable = gio::Cancellable::NONE;
241-
let stateroot = Some(stateroot);
242-
let merge_deployment = sysroot.merge_deployment(stateroot);
243-
let origin = glib::KeyFile::new();
244-
let ostree_imgref = spec
245-
.image
246-
.as_ref()
247-
.map(|imgref| OstreeImageReference::from(imgref.clone()));
248-
if let Some(imgref) = ostree_imgref.as_ref() {
249-
origin.set_string(
250-
"origin",
251-
ostree_container::deploy::ORIGIN_CONTAINER,
252-
imgref.to_string().as_str(),
253-
);
254-
}
255-
let _new_deployment = sysroot.stage_tree_with_options(
256-
stateroot,
257-
image.merge_commit.as_str(),
258-
Some(&origin),
259-
merge_deployment.as_ref(),
260-
&Default::default(),
261-
cancellable,
262-
)?;
263-
if let Some(imgref) = ostree_imgref.as_ref() {
264-
println!("Queued for next boot: {imgref}");
265-
}
266-
ostree_container::deploy::remove_undeployed_images(sysroot).context("Pruning images")?;
267-
268-
Ok(())
269-
}
270-
271230
#[context("Querying root privilege")]
272231
pub(crate) fn require_root() -> Result<()> {
273232
let uid = rustix::process::getuid();
@@ -282,7 +241,7 @@ pub(crate) fn require_root() -> Result<()> {
282241

283242
/// A few process changes that need to be made for writing.
284243
#[context("Preparing for write")]
285-
async fn prepare_for_write() -> Result<()> {
244+
pub(crate) async fn prepare_for_write() -> Result<()> {
286245
if ostree_ext::container_utils::is_ostree_container()? {
287246
anyhow::bail!(
288247
"Detected container (ostree base); this command requires a booted host system."
@@ -351,7 +310,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
351310
}
352311

353312
let osname = booted_deployment.osname();
354-
stage(sysroot, &osname, fetched, &host.spec).await?;
313+
crate::deploy::stage(sysroot, &osname, fetched, &host.spec).await?;
355314
}
356315
if let Some(path) = opts.touch_if_changed {
357316
std::fs::write(&path, "").with_context(|| format!("Writing {path}"))?;
@@ -410,7 +369,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
410369
}
411370

412371
let stateroot = booted_deployment.osname();
413-
stage(sysroot, &stateroot, fetched, &new_spec).await?;
372+
crate::deploy::stage(sysroot, &stateroot, fetched, &new_spec).await?;
414373

415374
Ok(())
416375
}
@@ -448,7 +407,7 @@ async fn edit(opts: EditOpts) -> Result<()> {
448407
// TODO gc old layers here
449408

450409
let stateroot = booted_deployment.osname();
451-
stage(sysroot, &stateroot, fetched, &new_host.spec).await?;
410+
crate::deploy::stage(sysroot, &stateroot, fetched, &new_host.spec).await?;
452411

453412
Ok(())
454413
}

lib/src/deploy.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
//! # Write deployments merging image with configmap
2+
//!
3+
//! Create a merged filesystem tree with the image and mounted configmaps.
4+
5+
use anyhow::{Context, Result};
6+
7+
use fn_error_context::context;
8+
use ostree::{gio, glib};
9+
use ostree_container::store::LayeredImageState;
10+
use ostree_container::OstreeImageReference;
11+
use ostree_ext::container as ostree_container;
12+
use ostree_ext::ostree;
13+
use ostree_ext::ostree::Deployment;
14+
use ostree_ext::sysroot::SysrootLock;
15+
16+
use crate::spec::HostSpec;
17+
18+
// TODO use https://github.com/ostreedev/ostree-rs-ext/pull/493/commits/afc1837ff383681b947de30c0cefc70080a4f87a
19+
const BASE_IMAGE_PREFIX: &str = "ostree/container/baseimage/bootc";
20+
21+
/// Set on an ostree commit if this is a derived commit
22+
const BOOTC_DERIVED_KEY: &str = "bootc.derived";
23+
24+
pub(crate) async fn cleanup(sysroot: &SysrootLock) -> Result<()> {
25+
let repo = sysroot.repo();
26+
let sysroot = sysroot.sysroot.clone();
27+
ostree_ext::tokio_util::spawn_blocking_cancellable_flatten(move |cancellable| {
28+
let cancellable = Some(cancellable);
29+
let repo = &repo;
30+
let txn = repo.auto_transaction(cancellable)?;
31+
let repo = txn.repo();
32+
33+
// Regenerate our base references. First, we delete the ones that exist
34+
for ref_entry in repo
35+
.list_refs_ext(
36+
Some(BASE_IMAGE_PREFIX),
37+
ostree::RepoListRefsExtFlags::NONE,
38+
cancellable,
39+
)
40+
.context("Listing refs")?
41+
.keys()
42+
{
43+
repo.transaction_set_refspec(ref_entry, None);
44+
}
45+
46+
// Then, for each deployment which is derived (e.g. has configmaps) we synthesize
47+
// a base ref to ensure that it's not GC'd.
48+
for (i, deployment) in sysroot.deployments().into_iter().enumerate() {
49+
let commit = deployment.csum();
50+
if let Some(base) = get_base_commit(repo, &commit)? {
51+
repo.transaction_set_refspec(&format!("{BASE_IMAGE_PREFIX}/{i}"), Some(&base));
52+
}
53+
}
54+
55+
Ok(())
56+
})
57+
.await
58+
}
59+
60+
/// If commit is a bootc-derived commit (e.g. has configmaps), return its base.
61+
#[context("Finding base commit")]
62+
pub(crate) fn get_base_commit<'a>(repo: &ostree::Repo, commit: &'a str) -> Result<Option<String>> {
63+
let commitv = repo.load_commit(&commit)?.0;
64+
let commitmeta = commitv.child_value(0);
65+
let commitmeta = &glib::VariantDict::new(Some(&commitmeta));
66+
let r = commitmeta
67+
.lookup::<String>(BOOTC_DERIVED_KEY)?
68+
.map(|v| v.to_string());
69+
Ok(r)
70+
}
71+
72+
#[context("Writing deployment")]
73+
async fn deploy(
74+
sysroot: &SysrootLock,
75+
merge_deployment: Option<&Deployment>,
76+
stateroot: &str,
77+
image: Box<LayeredImageState>,
78+
origin: &glib::KeyFile,
79+
) -> Result<()> {
80+
let stateroot = Some(stateroot);
81+
// Copy to move into thread
82+
let base_commit = image.get_commit().to_owned();
83+
let cancellable = gio::Cancellable::NONE;
84+
let _new_deployment = sysroot.stage_tree_with_options(
85+
stateroot,
86+
&base_commit,
87+
Some(origin),
88+
merge_deployment,
89+
&Default::default(),
90+
cancellable,
91+
)?;
92+
Ok(())
93+
}
94+
95+
/// Stage (queue deployment of) a fetched container image.
96+
#[context("Staging")]
97+
pub(crate) async fn stage(
98+
sysroot: &SysrootLock,
99+
stateroot: &str,
100+
image: Box<LayeredImageState>,
101+
spec: &HostSpec,
102+
) -> Result<()> {
103+
let merge_deployment = sysroot.merge_deployment(Some(stateroot));
104+
let origin = glib::KeyFile::new();
105+
let ostree_imgref = spec
106+
.image
107+
.as_ref()
108+
.map(|imgref| OstreeImageReference::from(imgref.clone()));
109+
if let Some(imgref) = ostree_imgref.as_ref() {
110+
origin.set_string(
111+
"origin",
112+
ostree_container::deploy::ORIGIN_CONTAINER,
113+
imgref.to_string().as_str(),
114+
);
115+
}
116+
crate::deploy::deploy(
117+
sysroot,
118+
merge_deployment.as_ref(),
119+
stateroot,
120+
image,
121+
&origin,
122+
)
123+
.await?;
124+
crate::deploy::cleanup(sysroot).await?;
125+
if let Some(imgref) = ostree_imgref.as_ref() {
126+
println!("Queued for next boot: {imgref}");
127+
}
128+
ostree_container::deploy::remove_undeployed_images(sysroot).context("Pruning images")?;
129+
130+
Ok(())
131+
}

lib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#![deny(clippy::todo)]
1515

1616
pub mod cli;
17+
pub(crate) mod deploy;
1718
mod lsm;
1819
mod reexec;
1920
mod status;

0 commit comments

Comments
 (0)