Skip to content
Merged
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
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ jobs:
# Install tests
sudo bootc-integration-tests install-alongside localhost/bootc

# system-reinstall-bootc tests
cargo build --release -p system-reinstall-bootc

# not sure why this is missing in the ubuntu image but just creating this directory allows the tests to pass
sudo mkdir -p /run/sshd

sudo install -m 0755 target/release/system-reinstall-bootc /usr/bin/system-reinstall-bootc
sudo podman pull quay.io/fedora/fedora-bootc:42
sudo bootc-integration-tests system-reinstall quay.io/fedora/fedora-bootc:42 --test-threads=1

# And the fsverity case
sudo podman run --privileged --pid=host localhost/bootc-fsverity bootc install to-existing-root --stateroot=other \
--acknowledge-destructive --skip-fetch-check
Expand Down
47 changes: 45 additions & 2 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 tests-integration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ fn-error-context = { workspace = true }
indoc = { workspace = true }
libtest-mimic = "0.8.0"
oci-spec = "0.7.0"
rexpect = "0.5"
rustix = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
Expand Down
5 changes: 5 additions & 0 deletions tests-integration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ full privileges, but is *not* destructive.

This suite is *DESTRUCTIVE*, executing the bootc `install to-existing-root`
style flow using the host root. Run it in a transient virtual machine.

### `system-reinstall`

This suite is *DESTRUCTIVE*, executing the `system-reinstall-bootc`
tests. Run it in a transient virtual machine.
6 changes: 3 additions & 3 deletions tests-integration/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ const NON_DEFAULT_STATEROOT: &str = "foo";

/// Clear out and delete any ostree roots, leverage bootc hidden wipe-ostree command to get rid of
/// otherwise hard to delete deployment files
fn reset_root(sh: &Shell, image: &str) -> Result<()> {
pub(crate) fn reset_root(sh: &Shell, image: &str) -> Result<()> {
delete_ostree_deployments(sh, image)?;
delete_ostree(sh)?;
Ok(())
}

fn delete_ostree(sh: &Shell) -> Result<(), anyhow::Error> {
pub(crate) fn delete_ostree(sh: &Shell) -> Result<(), anyhow::Error> {
if !Path::new("/ostree/").exists() {
return Ok(());
}
Expand Down Expand Up @@ -61,7 +61,7 @@ fn find_deployment_root() -> Result<Dir> {
}

// Hook relatively cheap post-install tests here
fn generic_post_install_verification() -> Result<()> {
pub(crate) fn generic_post_install_verification() -> Result<()> {
assert!(Utf8Path::new("/ostree/repo").try_exists()?);
assert!(Utf8Path::new("/ostree/bootc/storage/overlay").try_exists()?);
Ok(())
Expand Down
146 changes: 146 additions & 0 deletions tests-integration/src/system_reinstall.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use anyhow::{anyhow, Context, Result};
use fn_error_context::context;
use libtest_mimic::Trial;
use rexpect::session::PtySession;
use rustix::fs::statfs;
use std::{
fs::{self},
path::Path,
};

use crate::install;

const TIMEOUT: u64 = 120000;

fn get_deployment_dir() -> Result<std::path::PathBuf> {
let base_path = Path::new("/ostree/deploy/default/deploy");

let entries: Vec<fs::DirEntry> = fs::read_dir(base_path)
.with_context(|| format!("Failed to read directory: {}", base_path.display()))?
.filter_map(|entry| match entry {
Ok(e) if e.path().is_dir() => Some(e),
_ => None,
})
.collect::<Vec<_>>();

assert_eq!(
entries.len(),
1,
"Expected exactly one deployment directory"
);

let deploy_dir_entry = &entries[0];
assert!(
deploy_dir_entry.file_type()?.is_dir(),
"deployment directory entry is not a directory: {}",
base_path.display()
);

let hash = deploy_dir_entry.file_name();
let hash_str = hash
.to_str()
.ok_or_else(|| anyhow!("Deployment directory name {:?} is not valid UTF-8", hash))?;

println!("Using deployment directory: {}", hash_str);

Ok(base_path.join(hash_str))
}

#[context("System reinstall tests")]
pub(crate) fn run(image: &str, testargs: libtest_mimic::Arguments) -> Result<()> {
// Just leak the image name so we get a static reference as required by the test framework
let image: &'static str = String::from(image).leak();

let tests = [
Trial::test("default behavior", move || {
let sh = &xshell::Shell::new()?;
install::reset_root(sh, image)?;

let mut p: PtySession = rexpect::spawn(
format!("/usr/bin/system-reinstall-bootc {image}").as_str(),
Some(TIMEOUT),
)?;

// Basic flow stdout verification
p.exp_regex("Found only one user ([^:]+) with ([\\d]+) SSH authorized keys.")?;
p.exp_string("Would you like to import its SSH authorized keys")?;
p.exp_string("into the root user on the new bootc system?")?;
p.exp_string("Then you can login as root@ using those keys. [Y/n]")?;
p.send_line("a")?;

p.exp_string("Going to run command:")?;

p.exp_regex(format!("podman run --privileged --pid=host --user=root:root -v /var/lib/containers:/var/lib/containers -v /dev:/dev --security-opt label=type:unconfined_t -v /:/target -v /tmp/([^:]+):/bootc_authorized_ssh_keys/root {image} bootc install to-existing-root --acknowledge-destructive --skip-fetch-check --cleanup --root-ssh-authorized-keys /bootc_authorized_ssh_keys/root").as_str())?;
p.exp_string("NOTICE: This will replace the installed operating system and reboot. Are you sure you want to continue? [y/N]")?;

p.send_line("y")?;

p.exp_string(format!("Installing image: docker://{image}").as_str())?;
p.exp_string("Initializing ostree layout")?;
p.exp_string("Operation complete, rebooting in 10 seconds. Press Ctrl-C to cancel reboot, or press enter to continue immediately.")?;
p.send_control('c')?;

p.exp_eof()?;

install::generic_post_install_verification()?;

// Check for destructive cleanup and ssh key files
let target_deployment_dir =
get_deployment_dir().with_context(|| "Failed to get deployment directory")?;

let files = [
"usr/lib/bootc/fedora-bootc-destructive-cleanup",
"usr/lib/systemd/system/bootc-destructive-cleanup.service",
"usr/lib/systemd/system/multi-user.target.wants/bootc-destructive-cleanup.service",
"etc/tmpfiles.d/bootc-root-ssh.conf",
];

for f in files {
let full_path = target_deployment_dir.join(f);
assert!(
full_path.exists(),
"File not found: {}",
full_path.display()
);
}

Ok(())
}),
Trial::test("disk space check", move || {
let sh = &xshell::Shell::new()?;
install::reset_root(sh, image)?;

// Allocate a file with the size of the available space on the root partition
let stat = statfs("/")?;
let available_space_bytes: u64 = stat.f_bsize as u64 * stat.f_bavail as u64;
let file_size = available_space_bytes - (250 * 1024 * 1024); //leave 250 MiB free

let tempfile = tempfile::Builder::new().tempfile_in("/")?;
let tempfile_path = tempfile.path();

let file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(tempfile_path)?;

rustix::fs::fallocate(&file, rustix::fs::FallocateFlags::empty(), 0, file_size)?;

// Run system-reinstall-bootc
let mut p: PtySession = rexpect::spawn(
format!("/usr/bin/system-reinstall-bootc {image}").as_str(),
Some(TIMEOUT),
)?;

p.exp_regex("Found only one user ([^:]+) with ([\\d]+) SSH authorized keys.")?;
p.send_line("a")?;
p.exp_string("NOTICE: This will replace the installed operating system and reboot. Are you sure you want to continue? [y/N]")?;
p.send_line("y")?;
p.exp_string("Insufficient free space")?;
p.exp_eof()?;
Ok(())
}),
];

libtest_mimic::run(&testargs, tests.into()).exit()
}
8 changes: 8 additions & 0 deletions tests-integration/src/tests-integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@ mod hostpriv;
mod install;
mod runvm;
mod selinux;
mod system_reinstall;

#[derive(Debug, Parser)]
#[clap(name = "bootc-integration-tests", version, rename_all = "kebab-case")]
pub(crate) enum Opt {
SystemReinstall {
/// Source container image reference
image: String,
#[clap(flatten)]
testargs: libtest_mimic::Arguments,
},
InstallAlongside {
/// Source container image reference
image: String,
Expand Down Expand Up @@ -45,6 +52,7 @@ pub(crate) enum Opt {
fn main() {
let opt = Opt::parse();
let r = match opt {
Opt::SystemReinstall { image, testargs } => system_reinstall::run(&image, testargs),
Opt::InstallAlongside { image, testargs } => install::run_alongside(&image, testargs),
Opt::HostPrivileged { image, testargs } => hostpriv::run_hostpriv(&image, testargs),
Opt::Container { testargs } => container::run(testargs),
Expand Down