Skip to content

Commit

Permalink
Factor out staging/deployment module
Browse files Browse the repository at this point in the history
Prep for configmap support.
  • Loading branch information
cgwalters committed Aug 12, 2023
1 parent 221e382 commit e0045a6
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 46 deletions.
51 changes: 5 additions & 46 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,20 @@ use anyhow::{Context, Result};
use camino::Utf8PathBuf;
use clap::Parser;
use fn_error_context::context;
use ostree::{gio, glib};
use ostree::gio;
use ostree_container::store::LayeredImageState;
use ostree_container::store::PrepareResult;
use ostree_container::OstreeImageReference;
use ostree_ext::container as ostree_container;
use ostree_ext::container::SignatureSource;
use ostree_ext::keyfileext::KeyFileExt;
use ostree_ext::ostree;
use ostree_ext::sysroot::SysrootLock;
use std::ffi::OsString;
use std::io::Seek;
use std::os::unix::process::CommandExt;
use std::process::Command;

use crate::spec::Host;
use crate::spec::HostSpec;
use crate::spec::ImageReference;

/// Perform an upgrade operation
Expand Down Expand Up @@ -229,45 +227,6 @@ async fn pull(
Ok(import)
}

/// Stage (queue deployment of) a fetched container image.
#[context("Staging")]
async fn stage(
sysroot: &SysrootLock,
stateroot: &str,
image: Box<LayeredImageState>,
spec: &HostSpec,
) -> Result<()> {
let cancellable = gio::Cancellable::NONE;
let stateroot = Some(stateroot);
let merge_deployment = sysroot.merge_deployment(stateroot);
let origin = glib::KeyFile::new();
let ostree_imgref = spec
.image
.as_ref()
.map(|imgref| OstreeImageReference::from(imgref.clone()));
if let Some(imgref) = ostree_imgref.as_ref() {
origin.set_string(
"origin",
ostree_container::deploy::ORIGIN_CONTAINER,
imgref.to_string().as_str(),
);
}
let _new_deployment = sysroot.stage_tree_with_options(
stateroot,
image.merge_commit.as_str(),
Some(&origin),
merge_deployment.as_ref(),
&Default::default(),
cancellable,
)?;
if let Some(imgref) = ostree_imgref.as_ref() {
println!("Queued for next boot: {imgref}");
}
ostree_container::deploy::remove_undeployed_images(sysroot).context("Pruning images")?;

Ok(())
}

#[context("Querying root privilege")]
pub(crate) fn require_root() -> Result<()> {
let uid = rustix::process::getuid();
Expand All @@ -282,7 +241,7 @@ pub(crate) fn require_root() -> Result<()> {

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

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

let stateroot = booted_deployment.osname();
stage(sysroot, &stateroot, fetched, &new_spec).await?;
crate::deploy::stage(sysroot, &stateroot, fetched, &new_spec).await?;

Ok(())
}
Expand Down Expand Up @@ -448,7 +407,7 @@ async fn edit(opts: EditOpts) -> Result<()> {
// TODO gc old layers here

let stateroot = booted_deployment.osname();
stage(sysroot, &stateroot, fetched, &new_host.spec).await?;
crate::deploy::stage(sysroot, &stateroot, fetched, &new_host.spec).await?;

Ok(())
}
Expand Down
131 changes: 131 additions & 0 deletions lib/src/deploy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//! # Write deployments merging image with configmap
//!
//! Create a merged filesystem tree with the image and mounted configmaps.

use anyhow::{Context, Result};

use fn_error_context::context;
use ostree::{gio, glib};
use ostree_container::store::LayeredImageState;
use ostree_container::OstreeImageReference;
use ostree_ext::container as ostree_container;
use ostree_ext::ostree;
use ostree_ext::ostree::Deployment;
use ostree_ext::sysroot::SysrootLock;

use crate::spec::HostSpec;

// TODO use https://github.com/ostreedev/ostree-rs-ext/pull/493/commits/afc1837ff383681b947de30c0cefc70080a4f87a
const BASE_IMAGE_PREFIX: &str = "ostree/container/baseimage/bootc";

/// Set on an ostree commit if this is a derived commit
const BOOTC_DERIVED_KEY: &str = "bootc.derived";

pub(crate) async fn cleanup(sysroot: &SysrootLock) -> Result<()> {
let repo = sysroot.repo();
let sysroot = sysroot.sysroot.clone();
ostree_ext::tokio_util::spawn_blocking_cancellable_flatten(move |cancellable| {
let cancellable = Some(cancellable);
let repo = &repo;
let txn = repo.auto_transaction(cancellable)?;
let repo = txn.repo();

// Regenerate our base references. First, we delete the ones that exist
for ref_entry in repo
.list_refs_ext(
Some(BASE_IMAGE_PREFIX),
ostree::RepoListRefsExtFlags::NONE,
cancellable,
)
.context("Listing refs")?
.keys()
{
repo.transaction_set_refspec(ref_entry, None);
}

// Then, for each deployment which is derived (e.g. has configmaps) we synthesize
// a base ref to ensure that it's not GC'd.
for (i, deployment) in sysroot.deployments().into_iter().enumerate() {
let commit = deployment.csum();
if let Some(base) = get_base_commit(repo, &commit)? {
repo.transaction_set_refspec(&format!("{BASE_IMAGE_PREFIX}/{i}"), Some(&base));
}
}

Ok(())
})
.await
}

/// If commit is a bootc-derived commit (e.g. has configmaps), return its base.
#[context("Finding base commit")]
pub(crate) fn get_base_commit<'a>(repo: &ostree::Repo, commit: &'a str) -> Result<Option<String>> {
let commitv = repo.load_commit(&commit)?.0;
let commitmeta = commitv.child_value(0);
let commitmeta = &glib::VariantDict::new(Some(&commitmeta));
let r = commitmeta
.lookup::<String>(BOOTC_DERIVED_KEY)?
.map(|v| v.to_string());
Ok(r)
}

#[context("Writing deployment")]
async fn deploy(
sysroot: &SysrootLock,
merge_deployment: Option<&Deployment>,
stateroot: &str,
image: Box<LayeredImageState>,
origin: &glib::KeyFile,
) -> Result<()> {
let stateroot = Some(stateroot);
// Copy to move into thread
let base_commit = image.get_commit().to_owned();
let cancellable = gio::Cancellable::NONE;
let _new_deployment = sysroot.stage_tree_with_options(
stateroot,
&base_commit,
Some(origin),
merge_deployment,
&Default::default(),
cancellable,
)?;
Ok(())
}

/// Stage (queue deployment of) a fetched container image.
#[context("Staging")]
pub(crate) async fn stage(
sysroot: &SysrootLock,
stateroot: &str,
image: Box<LayeredImageState>,
spec: &HostSpec,
) -> Result<()> {
let merge_deployment = sysroot.merge_deployment(Some(stateroot));
let origin = glib::KeyFile::new();
let ostree_imgref = spec
.image
.as_ref()
.map(|imgref| OstreeImageReference::from(imgref.clone()));
if let Some(imgref) = ostree_imgref.as_ref() {
origin.set_string(
"origin",
ostree_container::deploy::ORIGIN_CONTAINER,
imgref.to_string().as_str(),
);
}
crate::deploy::deploy(
sysroot,
merge_deployment.as_ref(),
stateroot,
image,
&origin,
)
.await?;
crate::deploy::cleanup(sysroot).await?;
if let Some(imgref) = ostree_imgref.as_ref() {
println!("Queued for next boot: {imgref}");
}
ostree_container::deploy::remove_undeployed_images(sysroot).context("Pruning images")?;

Ok(())
}
1 change: 1 addition & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#![deny(clippy::todo)]

pub mod cli;
pub(crate) mod deploy;
mod lsm;
mod reexec;
mod status;
Expand Down

0 comments on commit e0045a6

Please sign in to comment.