Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for (weakly) "lifecycle bound" podman images #559

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ tempfile = "3.10.1"
toml = "0.8.12"
xshell = { version = "0.2.6", optional = true }
uuid = { version = "1.8.0", features = ["v4"] }
rust-ini = "0.21.0"

[features]
default = ["install"]
Expand Down
81 changes: 70 additions & 11 deletions lib/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
//!
//! Create a merged filesystem tree with the image and mounted configmaps.

use std::collections::HashSet;
use std::io::{BufRead, Write};
use std::process::Command;

use anyhow::Ok;
use anyhow::{anyhow, Context, Result};
Expand All @@ -18,7 +20,9 @@ use ostree_ext::container::store::PrepareResult;
use ostree_ext::ostree;
use ostree_ext::ostree::Deployment;
use ostree_ext::sysroot::SysrootLock;
use rustix::fd::BorrowedFd;

use crate::podman;
use crate::spec::ImageReference;
use crate::spec::{BootOrder, HostSpec};
use crate::status::labels_of_config;
Expand Down Expand Up @@ -113,6 +117,53 @@ pub(crate) fn check_bootc_label(config: &ostree_ext::oci_spec::image::ImageConfi
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct BoundState {
pub(crate) total_images: usize,
pub(crate) bound_images: HashSet<String>,
}

impl BoundState {
pub(crate) fn is_empty(&self) -> bool {
self.bound_images.is_empty()
}

pub(crate) fn print(&self) {
if self.total_images == 0 {
println!("No podman .image definitions found");
} else {
println!("podman systemd .image entries: {}", self.total_images);
println!("Bound images: {}", self.bound_images.len());
}
}
}

pub(crate) fn query_bound_state(root: &Dir) -> Result<BoundState> {
let (total_images, bound_images) = podman::list_container_images(root)?;
tracing::debug!("images={total_images} bound={}", bound_images.len());
Ok(BoundState {
total_images,
bound_images,
})
}

/// Pre-fetch e.g. podman `.image` files which reference external images. This
/// expects that podman sees e.g. `/var` set up as the deployment root.
#[context("Fetching bound state")]
pub(crate) async fn fetch_bound_state(state: &BoundState) -> Result<()> {
for image in state.bound_images.iter() {
let mut cmd = Command::new("podman");
cmd.args(["pull", image.as_str()]);
let mut cmd = tokio::process::Command::from(cmd);
cmd.kill_on_drop(true);
let status = cmd.status().await.context("bound podman pull")?;
if !status.success() {
anyhow::bail!("Failed to pull {image}");
}
}
Ok(())
}

/// Write container fetch progress to standard output.
async fn handle_layer_progress_print(
mut layers: tokio::sync::mpsc::Receiver<ostree_container::store::ImportProgress>,
Expand Down Expand Up @@ -278,19 +329,20 @@ async fn deploy(
stateroot: &str,
image: &ImageState,
origin: &glib::KeyFile,
) -> Result<()> {
) -> Result<Deployment> {
let stateroot = Some(stateroot);
// Copy to move into thread
let cancellable = gio::Cancellable::NONE;
let _new_deployment = sysroot.stage_tree_with_options(
stateroot,
image.ostree_commit.as_str(),
Some(origin),
merge_deployment,
&Default::default(),
cancellable,
)?;
Ok(())
sysroot
.stage_tree_with_options(
stateroot,
image.ostree_commit.as_str(),
Some(origin),
merge_deployment,
&Default::default(),
cancellable,
)
.map_err(Into::into)
}

#[context("Generating origin")]
Expand All @@ -307,6 +359,7 @@ fn origin_from_imageref(imgref: &ImageReference) -> Result<glib::KeyFile> {

/// Stage (queue deployment of) a fetched container image.
#[context("Staging")]
#[allow(unsafe_code)]
pub(crate) async fn stage(
sysroot: &SysrootLock,
stateroot: &str,
Expand All @@ -315,14 +368,20 @@ pub(crate) async fn stage(
) -> Result<()> {
let merge_deployment = sysroot.merge_deployment(Some(stateroot));
let origin = origin_from_imageref(spec.image)?;
crate::deploy::deploy(
let deployment = crate::deploy::deploy(
sysroot,
merge_deployment.as_ref(),
stateroot,
image,
&origin,
)
.await?;
let sysroot_fd = Dir::reopen_dir(unsafe { &BorrowedFd::borrow_raw(sysroot.fd()) })?;
let deployment_dir = sysroot_fd.open_dir(sysroot.deployment_dirpath(&deployment))?;
// TODO: Make things atomic here by not completing the staging unless we can fetch
// the new images.
let bound = query_bound_state(&deployment_dir)?;
fetch_bound_state(&bound).await?;
crate::deploy::cleanup(sysroot).await?;
println!("Queued for next boot: {:#}", spec.image);
if let Some(version) = image.version.as_deref() {
Expand Down
51 changes: 40 additions & 11 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use serde::{Deserialize, Serialize};

use self::baseline::InstallBlockDeviceOpts;
use crate::containerenv::ContainerExecutionInfo;
use crate::deploy::query_bound_state;
use crate::mount::Filesystem;
use crate::task::Task;
use crate::utils::sigpolicy_from_opts;
Expand Down Expand Up @@ -530,11 +531,17 @@ pub(crate) fn print_configuration() -> Result<()> {
serde_json::to_writer(stdout, &install_config).map_err(Into::into)
}

pub(crate) struct InitializedRoot {
aleph: InstallAleph,
deployment: Dir,
var: Dir,
}

#[context("Creating ostree deployment")]
async fn initialize_ostree_root_from_self(
state: &State,
root_setup: &RootSetup,
) -> Result<InstallAleph> {
) -> Result<InitializedRoot> {
let sepolicy = state.load_policy()?;
let sepolicy = sepolicy.as_ref();

Expand Down Expand Up @@ -667,6 +674,10 @@ async fn initialize_ostree_root_from_self(
let root = rootfs_dir
.open_dir(path.as_str())
.context("Opening deployment dir")?;
let varpath = format!("ostree/deploy/{stateroot}/var");
let var = rootfs_dir
.open_dir(&varpath)
.with_context(|| format!("Opening {varpath}"))?;

// And do another recursive relabeling pass over the ostree-owned directories
// but avoid recursing into the deployment root (because that's a *distinct*
Expand Down Expand Up @@ -715,7 +726,11 @@ async fn initialize_ostree_root_from_self(
selinux: state.selinux_state.to_aleph().to_string(),
};

Ok(aleph)
Ok(InitializedRoot {
aleph,
deployment: root,
var: var,
})
}

/// Run a command in the host mount namespace
Expand Down Expand Up @@ -1180,15 +1195,29 @@ async fn install_to_filesystem_impl(state: &State, rootfs: &mut RootSetup) -> Re
tracing::debug!("boot uuid={boot_uuid}");

// Write the aleph data that captures the system state at the time of provisioning for aid in future debugging.
{
let aleph = initialize_ostree_root_from_self(state, rootfs).await?;
rootfs
.rootfs_fd
.atomic_replace_with(BOOTC_ALEPH_PATH, |f| {
serde_json::to_writer(f, &aleph)?;
anyhow::Ok(())
})
.context("Writing aleph version")?;
let inst = initialize_ostree_root_from_self(state, rootfs).await?;
rootfs
.rootfs_fd
.atomic_replace_with(BOOTC_ALEPH_PATH, |f| {
serde_json::to_writer(f, &inst.aleph)?;
anyhow::Ok(())
})
.context("Writing aleph version")?;

let bound = query_bound_state(&inst.deployment)?;
bound.print();
if !bound.is_empty() {
println!();
Task::new("Mounting deployment /var", "mount")
.args(["--bind", ".", "/var"])
.cwd(&inst.var)?
.run()?;
// podman needs this
Task::new("Initializing /var/tmp", "systemd-tmpfiles")
.args(["--create", "--boot", "--prefix=/var/tmp"])
.verbose()
.run()?;
crate::deploy::fetch_bound_state(&bound).await?;
}

crate::bootloader::install_via_bootupd(&rootfs.device, &rootfs.rootfs, &state.config_opts)?;
Expand Down
1 change: 0 additions & 1 deletion lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ mod k8sapitypes;
mod kernel;
#[cfg(feature = "install")]
pub(crate) mod mount;
#[cfg(feature = "install")]
mod podman;
pub mod spec;

Expand Down
Loading
Loading