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

WIP: takeover installs #78

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
12 changes: 12 additions & 0 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ pub(crate) enum TestingOpts {
RunPrivilegedIntegration {},
/// Execute integration tests that target a not-privileged ostree container
RunContainerIntegration {},
/// Copy the container as ostree commit to target root
CopySelfTo { target: Utf8PathBuf },
/// Block device setup for testing
PrepTestInstallFilesystem { blockdev: Utf8PathBuf },
/// e2e test of install-to-filesystem
Expand Down Expand Up @@ -461,6 +463,16 @@ where
I: IntoIterator,
I::Item: Into<OsString> + Clone,
{
let args = args
.into_iter()
.map(|v| Into::<OsString>::into(v))
.collect::<Vec<_>>();
if matches!(
args.get(0).and_then(|v| v.to_str()),
Some(crate::systemtakeover::BIN_NAME)
) {
return crate::systemtakeover::run().await;
}
run_from_opt(Opt::parse_from(args)).await
}

Expand Down
122 changes: 89 additions & 33 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

// This sub-module is the "basic" installer that handles creating basic block device
// and filesystem setup.
mod baseline;
pub(crate) mod baseline;

use std::io::BufWriter;
use std::io::Write;
Expand Down Expand Up @@ -37,6 +37,9 @@ use crate::containerenv::ContainerExecutionInfo;
use crate::task::Task;
use crate::utils::run_in_host_mountns;

/// The path we use to access files on the host
pub(crate) const HOST_RUNDIR: &str = "/run/host";

/// The default "stateroot" or "osname"; see https://github.com/ostreedev/ostree/issues/2794
const STATEROOT_DEFAULT: &str = "default";
/// The toplevel boot directory
Expand Down Expand Up @@ -196,6 +199,8 @@ pub(crate) struct SourceInfo {
pub(crate) commit: String,
/// Whether or not SELinux appears to be enabled in the source commit
pub(crate) selinux: bool,
/// If we should find the image in sysroot/repo, not in containers/storage
pub(crate) from_ostree_repo: bool,
}

// Shared read-only global state
Expand Down Expand Up @@ -345,11 +350,13 @@ impl SourceInfo {
let root = root.downcast_ref::<ostree::RepoFile>().unwrap();
let xattrs = root.xattrs(cancellable)?;
let selinux = crate::lsm::xattrs_have_selinux(&xattrs);
let from_ostree_repo = false;
Ok(Self {
imageref,
digest,
commit,
selinux,
from_ostree_repo,
})
}
}
Expand Down Expand Up @@ -424,6 +431,14 @@ pub(crate) mod config {
}
}

pub(crate) fn import_config_from_host() -> ostree_container::store::ImageProxyConfig {
let skopeo_cmd = run_in_host_mountns("skopeo");
ostree_container::store::ImageProxyConfig {
skopeo_cmd: Some(skopeo_cmd),
..Default::default()
}
}

#[context("Creating ostree deployment")]
async fn initialize_ostree_root_from_self(
state: &State,
Expand Down Expand Up @@ -492,36 +507,10 @@ async fn initialize_ostree_root_from_self(

let sysroot = ostree::Sysroot::new(Some(&gio::File::for_path(rootfs)));
sysroot.load(cancellable)?;
let dest_repo = &sysroot.repo();

// We need to fetch the container image from the root mount namespace
let skopeo_cmd = run_in_host_mountns("skopeo");
let proxy_cfg = ostree_container::store::ImageProxyConfig {
skopeo_cmd: Some(skopeo_cmd),
..Default::default()
};

let mut temporary_dir = None;
let src_imageref = if skopeo_supports_containers_storage()? {
// We always use exactly the digest of the running image to ensure predictability.
let spec =
crate::utils::digested_pullspec(&state.source.imageref.name, &state.source.digest);
ostree_container::ImageReference {
transport: ostree_container::Transport::ContainerStorage,
name: spec,
}
} else {
let td = tempfile::tempdir_in("/var/tmp")?;
let path: &Utf8Path = td.path().try_into().unwrap();
let r = copy_to_oci(&state.source.imageref, path)?;
temporary_dir = Some(td);
r
};
let src_imageref = ostree_container::OstreeImageReference {
// There are no signatures to verify since we're fetching the already
// pulled container.
sigverify: ostree_container::SignatureSource::ContainerPolicyAllowInsecure,
imgref: src_imageref,
};
let proxy_cfg = import_config_from_host();

let kargs = root_setup
.kargs
Expand All @@ -532,6 +521,56 @@ async fn initialize_ostree_root_from_self(
options.kargs = Some(kargs.as_slice());
options.target_imgref = Some(&target_imgref);
options.proxy_cfg = Some(proxy_cfg);

// Default image reference pulls from the running container image.
let mut src_imageref = ostree_container::OstreeImageReference {
// There are no signatures to verify since we're fetching the already
// pulled container.
sigverify: SignatureSource::ContainerPolicyAllowInsecure,
imgref: state.source.imageref.clone(),
};

let mut temporary_dir = None;
if state.source.from_ostree_repo {
let root = Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
let host_repo = {
let repodir = root
.open_dir("sysroot/repo")
.context("Opening sysroot/repo")?;
ostree::Repo::open_at_dir(repodir.as_fd(), ".")?
};
ostree_container::store::copy_as(
&host_repo,
&state.source.imageref,
&dest_repo,
&target_imgref.imgref,
)
.await
.context("Copying image from host repo")?;
// We already copied the image, so src == target
src_imageref = target_imgref.clone();
options.target_imgref = None;
} else {
if skopeo_supports_containers_storage()? {
// We always use exactly the digest of the running image to ensure predictability.
let spec =
crate::utils::digested_pullspec(&state.source.imageref.name, &state.source.digest);
ostree_container::ImageReference {
transport: ostree_container::Transport::ContainerStorage,
name: spec,
}
} else {
let td = tempfile::tempdir_in("/var/tmp")?;
let path: &Utf8Path = td.path().try_into().unwrap();
let r = copy_to_oci(&state.source.imageref, path)?;
temporary_dir = Some(td);
r
};
// In this case the deploy code is pulling the container, so set it up to
// generate a target image reference.
options.target_imgref = Some(&target_imgref);
}

println!("Creating initial deployment");
let state =
ostree_container::deploy::deploy(&sysroot, stateroot, &src_imageref, Some(options)).await?;
Expand Down Expand Up @@ -884,11 +923,16 @@ fn installation_complete() {
println!("Installation complete!");
}

/// Implementation of the `bootc install` CLI command.
pub(crate) async fn install(opts: InstallOpts) -> Result<()> {
let block_opts = opts.block_opts;
let state = prepare_install(opts.config_opts, opts.target_opts).await?;
pub(crate) async fn install_takeover(
opts: InstallBlockDeviceOpts,
state: Arc<State>,
) -> Result<()> {
// The takeover code should have unset this
assert!(!opts.takeover);
block_install_impl(opts, state).await
}

async fn block_install_impl(block_opts: InstallBlockDeviceOpts, state: Arc<State>) -> Result<()> {
// This is all blocking stuff
let mut rootfs = {
let state = state.clone();
Expand All @@ -914,6 +958,18 @@ pub(crate) async fn install(opts: InstallOpts) -> Result<()> {
Ok(())
}

/// Implementation of the `bootc install` CLI command.
pub(crate) async fn install(opts: InstallOpts) -> Result<()> {
let block_opts = opts.block_opts;
let state = prepare_install(opts.config_opts, opts.target_opts).await?;
if block_opts.takeover {
tracing::debug!("Performing takeover installation from host");
return crate::systemtakeover::run_from_host(block_opts, state).await;
}

block_install_impl(block_opts, state).await
}

#[context("Verifying empty rootfs")]
fn require_empty_rootdir(rootfs_fd: &Dir) -> Result<()> {
for e in rootfs_fd.entries()? {
Expand Down
7 changes: 7 additions & 0 deletions lib/src/install/baseline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ pub(crate) struct InstallBlockDeviceOpts {
#[serde(default)]
pub(crate) wipe: bool,

/// Write to the block device containing the running root filesystem.
/// This is implemented by moving the container into memory and switching
/// root (terminating all other processes).
#[clap(long)]
#[serde(default)]
pub(crate) takeover: bool,

/// Target root block device setup.
///
/// direct: Filesystem written directly to block device
Expand Down
2 changes: 2 additions & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub(crate) mod mount;
mod podman;
pub mod spec;
#[cfg(feature = "install")]
pub(crate) mod systemtakeover;
#[cfg(feature = "install")]
mod task;

#[cfg(feature = "docgen")]
Expand Down
14 changes: 14 additions & 0 deletions lib/src/privtests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::process::Command;

use anyhow::Result;
use camino::{Utf8Path, Utf8PathBuf};
use cap_std::fs::Dir;
use cap_std_ext::cap_std;
use fn_error_context::context;
use rustix::fd::AsFd;
use xshell::{cmd, Shell};
Expand Down Expand Up @@ -169,6 +171,18 @@ pub(crate) async fn run(opts: TestingOpts) -> Result<()> {
TestingOpts::RunContainerIntegration {} => {
tokio::task::spawn_blocking(impl_run_container).await?
}
TestingOpts::CopySelfTo { target } => {
let src_root = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
let target = &Dir::open_ambient_dir(target, cap_std::ambient_authority())?;
let container_info = crate::containerenv::get_container_execution_info(src_root)?;
let srcdata = crate::install::SourceInfo::from_container(&container_info)?;
let (did_override, _guard) =
crate::install::reexecute_self_for_selinux_if_needed(&srcdata, false)?;
// Right now we don't expose an override flow
assert!(!did_override);
crate::systemtakeover::copy_self_to(&srcdata, target).await?;
Ok(())
}
TestingOpts::PrepTestInstallFilesystem { blockdev } => {
tokio::task::spawn_blocking(move || prep_test_install_filesystem(&blockdev).map(|_| ()))
.await?
Expand Down
Loading
Loading