Skip to content
Open
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
67 changes: 67 additions & 0 deletions crates/lib/src/bootc_composefs/backwards_compat/bcompat_boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{
ComposefsCmdline, get_bootloader, get_sorted_grub_uki_boot_entries,
get_sorted_type1_boot_entries,
},
utils::stage_non_manged_bls_entries,
},
composefs_consts::{
ORIGIN_KEY_BOOT, ORIGIN_KEY_BOOT_TYPE, STATE_DIR_RELATIVE, TYPE1_BOOT_DIR_PREFIX,
Expand Down Expand Up @@ -244,6 +245,8 @@ fn create_staged_bls_entries(boot_dir: &Dir, entries: &Vec<(String, BLSConfig)>)
staged_entries.atomic_write(filename, new_entry.to_string().as_bytes())?;
}

stage_non_manged_bls_entries(boot_dir, &staged_entries)?;

fsync(staged_entries.reopen_as_ownedfd()?).context("fsync")
}

Expand Down Expand Up @@ -396,3 +399,67 @@ pub(crate) async fn prepend_custom_prefix(

Ok(())
}

#[cfg(test)]
mod tests {
use crate::bootc_composefs::backwards_compat::bcompat_boot::*;
use crate::parsers::bls_config::parse_bls_config;
use anyhow::Result;
use cap_std_ext::{cap_std, dirext::CapStdExtDirExt};

#[test]
fn test_create_staged_bls_entries() -> Result<()> {
let tempdir = cap_std_ext::cap_tempfile::tempdir(cap_std::ambient_authority())?;

let entry1 = r#"
title Fedora 42.20250623.3.1 (CoreOS)
version fedora-42.0
sort-key 1
linux /boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/vmlinuz-5.14.10
initrd /boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/initramfs-5.14.10.img
options root=UUID=abc123 rw composefs=7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6
"#;
let entry2 = r#"
title Fedora 41.20250214.2.0 (CoreOS)
version fedora-42.0
sort-key 2
efi /EFI/Linux/f7415d75017a12a387a39d2281e033a288fc15775108250ef70a01dcadb93346.efi
options root=UUID=abc123 rw composefs=febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01
"#;
let entry3 = r#"
title Test
version 1
sort-key 9
efi /EFI/boot/test.efi
"#;

tempdir.create_dir_all("loader/entries")?;
tempdir.atomic_write("loader/entries/bootc_entry1.conf", entry1)?;
tempdir.atomic_write("loader/entries/bootc_entry2.conf", entry2)?;
tempdir.atomic_write("loader/entries/entry3.conf", entry3)?;

let entries = vec![
(
"bootc_entry1.conf".to_string(),
parse_bls_config(entry1).unwrap(),
),
(
"bootc_entry2.conf".to_string(),
parse_bls_config(entry2).unwrap(),
),
];

create_staged_bls_entries(&tempdir, &entries).unwrap();

assert!(tempdir.exists("loader/entries.staged/bootc_entry1.conf"));
assert!(tempdir.exists("loader/entries.staged/bootc_entry2.conf"));
assert!(tempdir.exists("loader/entries.staged/entry3.conf"));

assert_eq!(
str::from_utf8(&tempdir.read("loader/entries.staged/entry3.conf")?[..])?,
entry3
);

Ok(())
}
}
106 changes: 106 additions & 0 deletions crates/lib/src/bootc_composefs/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ use serde::{Deserialize, Serialize};

use crate::bootc_composefs::state::{get_booted_bls, write_composefs_state};
use crate::bootc_composefs::status::ComposefsCmdline;
use crate::bootc_composefs::utils::stage_non_manged_bls_entries;
use crate::bootc_kargs::compute_new_kargs;
use crate::composefs_consts::{TYPE1_BOOT_DIR_PREFIX, TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED};
use crate::parsers::bls_config::{BLSConfig, BLSConfigType, EFIKey};
Expand Down Expand Up @@ -1254,6 +1255,10 @@ fn write_systemd_uki_config(
)?;
}

if let BootSetupType::Upgrade(_) = setup_type {
stage_non_manged_bls_entries(esp_dir, &entries_dir)?;
}

// Write the timeout for bootloader menu if not exists
if !esp_dir.exists(SYSTEMD_LOADER_CONF_PATH) {
esp_dir
Expand Down Expand Up @@ -1773,6 +1778,13 @@ pub(crate) async fn setup_composefs_boot(

#[cfg(test)]
mod tests {
use cap_std::fs::Dir;
use cap_std_ext::cap_std;
use ostree_ext::{ostree::SysrootBuilder, sysroot::SysrootLock};
use std::fs::File;

use crate::{bootc_composefs::digest::new_temp_composefs_repo, k8sapitypes::Resource};

use super::*;

#[test]
Expand Down Expand Up @@ -1928,4 +1940,98 @@ mod tests {
"RHEL should sort before Fedora in descending order"
);
}

#[tokio::test]
async fn test_write_systemd_uki_config() -> Result<()> {
let tempdir = cap_std_ext::cap_tempfile::tempdir(cap_std::ambient_authority())?;

let entry1 = r#"
title Fedora 42.20250623.3.1 (CoreOS)
version fedora-42.0
sort-key 1
linux /boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/vmlinuz-5.14.10
initrd /boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/initramfs-5.14.10.img
options root=UUID=abc123 rw composefs=7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6
"#;
let entry2 = r#"
title Fedora 41.20250214.2.0 (CoreOS)
version fedora-42.0
sort-key 2
efi /EFI/Linux/f7415d75017a12a387a39d2281e033a288fc15775108250ef70a01dcadb93346.efi
options root=UUID=abc123 rw composefs=febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01
"#;
let entry3 = r#"
title Test
version 1
sort-key 9
efi /EFI/boot/test.efi
"#;

tempdir.create_dir_all("loader/entries")?;
tempdir.atomic_write("loader/entries/bootc_entry1.conf", entry1)?;
tempdir.atomic_write("loader/entries/bootc_entry2.conf", entry2)?;
tempdir.atomic_write("loader/entries/entry3.conf", entry3)?;

let sysroot_path = format!(
"{}/test_write_systemd_uki_config",
std::env::temp_dir().display()
);
std::fs::create_dir_all(&sysroot_path)?;
let dir = Dir::from_std_file(File::open(&sysroot_path)?);
let sysroot_dir = cap_std_ext::cap_tempfile::tempdir_in(&dir)?;
sysroot_dir.create_dir_all("ostree/repo")?;
sysroot_dir.atomic_write(
"ostree/repo/config",
r#"[core]
repo_version=1
mode=bare-split-xattrs
"#,
)?;

let sysroot = SysrootBuilder::new()
.path(Some(sysroot_path.into()))
.create(None)?;
let sysroot_lock = SysrootLock::new_from_sysroot(&sysroot).await?;
let storage = Storage::new_ostree(sysroot_lock, &sysroot_dir)?;
let cmdline = Box::new(ComposefsCmdline::new(Default::default()));
let (_repo_dir, repo) = new_temp_composefs_repo()?;
let booted_composefs = BootedComposefs {
repo: repo,
cmdline: Box::leak(cmdline),
};
let host = Host {
resource: Resource {
api_version: Default::default(),
kind: Default::default(),
metadata: Default::default(),
},
spec: Default::default(),
status: Default::default(),
};
let boot_setup_type = BootSetupType::Upgrade((&storage, &booted_composefs, &host));
let boot_label = UKIInfo {
boot_label: "label".to_string(),
version: None,
os_id: None,
boot_digest: "".to_string(),
};
let id = Sha512HashValue::EMPTY;
let bootloader = Bootloader::Systemd;

write_systemd_uki_config(&tempdir, &boot_setup_type, boot_label, &id, &bootloader)?;

assert!(tempdir.exists(SYSTEMD_LOADER_CONF_PATH));
assert!(tempdir.exists("loader/entries.staged"));
assert!(tempdir.exists("loader/entries.staged/bootc_bootc-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000-1.conf"));
assert!(tempdir.exists("loader/entries.staged/bootc_bootc-fedora-42.0-0.conf"));
assert!(!tempdir.exists("loader/entries.staged/bootc_bootc-fedora-42.0-1.conf"));
assert!(tempdir.exists("loader/entries.staged/entry3.conf"));

assert_eq!(
str::from_utf8(&tempdir.read("loader/entries.staged/entry3.conf")?[..])?,
entry3
);

Ok(())
}
}
70 changes: 70 additions & 0 deletions crates/lib/src/bootc_composefs/rollback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::bootc_composefs::boot::{
secondary_sort_key, type1_entry_conf_file_name,
};
use crate::bootc_composefs::status::{get_composefs_status, get_sorted_type1_boot_entries};
use crate::bootc_composefs::utils::stage_non_manged_bls_entries;
use crate::composefs_consts::{
COMPOSEFS_STAGED_DEPLOYMENT_FNAME, COMPOSEFS_TRANSIENT_STATE_DIR, TYPE1_ENT_PATH_STAGED,
};
Expand Down Expand Up @@ -198,6 +199,8 @@ fn rollback_composefs_entries(host: &Host, boot_dir: &Dir, bootloader: Bootloade
.with_context(|| format!("Writing to {file_name}"))?;
}

stage_non_manged_bls_entries(boot_dir, &rollback_entries_dir)?;

let rollback_entries_dir = rollback_entries_dir
.reopen_as_ownedfd()
.context("Reopening as owned fd")?;
Expand Down Expand Up @@ -276,3 +279,70 @@ pub(crate) async fn composefs_rollback(

Ok(())
}

#[cfg(test)]
mod tests {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are fine to have, but having proper integration tests in the tmt/tests directory would be much nicer

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I has some difficulty setting up an environment where I can run the integration tests. The existing tests should be green in a clean fedora cloud image after installing the packages from the contributing document, but they're not...

I'll see if I can figure it out

use anyhow::{Ok, Result};
use cap_std_ext::{cap_std, dirext::CapStdExtDirExt};

use crate::k8sapitypes::Resource;
use crate::spec::Bootloader;
use crate::{bootc_composefs::rollback::rollback_composefs_entries, spec::Host};

#[test]
fn test_rollback_composefs_entries() -> Result<()> {
let tempdir = cap_std_ext::cap_tempfile::tempdir(cap_std::ambient_authority())?;

let entry1 = r#"
title Fedora 42.20250623.3.1 (CoreOS)
version fedora-42.0
sort-key 1
linux /boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/vmlinuz-5.14.10
initrd /boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/initramfs-5.14.10.img
options root=UUID=abc123 rw composefs=7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6
"#;
let entry2 = r#"
title Fedora 41.20250214.2.0 (CoreOS)
version fedora-42.0
sort-key 2
efi /EFI/Linux/f7415d75017a12a387a39d2281e033a288fc15775108250ef70a01dcadb93346.efi
options root=UUID=abc123 rw composefs=febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01
"#;
let entry3 = r#"
title Test
version 1
sort-key 9
efi /EFI/boot/test.efi
"#;

tempdir.create_dir_all("loader/entries")?;
tempdir.atomic_write("loader/entries/bootc_entry1.conf", entry1)?;
tempdir.atomic_write("loader/entries/bootc_entry2.conf", entry2)?;
tempdir.atomic_write("loader/entries/entry3.conf", entry3)?;

let host = Host {
resource: Resource {
api_version: Default::default(),
kind: Default::default(),
metadata: Default::default(),
},
spec: Default::default(),
status: Default::default(),
};
let bootloader = Bootloader::None;

rollback_composefs_entries(&host, &tempdir, bootloader)?;

assert!(tempdir.exists("loader/entries"));
assert!(tempdir.exists("loader/entries/bootc_bootc-fedora-42.0-0.conf"));
assert!(tempdir.exists("loader/entries/bootc_bootc-fedora-42.0-1.conf"));
assert!(tempdir.exists("loader/entries/entry3.conf"));

assert_eq!(
str::from_utf8(&tempdir.read("loader/entries/entry3.conf")?[..])?,
entry3
);

Ok(())
}
}
25 changes: 17 additions & 8 deletions crates/lib/src/bootc_composefs/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ use crate::{
utils::{compute_store_boot_digest_for_uki, get_uki_cmdline},
},
composefs_consts::{
COMPOSEFS_CMDLINE, ORIGIN_KEY_BOOT_DIGEST, ORIGIN_KEY_IMAGE, ORIGIN_KEY_MANIFEST_DIGEST,
TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED, USER_CFG, USER_CFG_STAGED,
BLS_ENTRY_PREFIX, COMPOSEFS_CMDLINE, ORIGIN_KEY_BOOT_DIGEST, ORIGIN_KEY_IMAGE,
ORIGIN_KEY_MANIFEST_DIGEST, TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED, USER_CFG,
USER_CFG_STAGED,
},
install::EFI_LOADER_INFO,
parsers::{
Expand Down Expand Up @@ -305,7 +306,7 @@ fn get_sorted_type1_boot_entries_helper(
.to_str()
.ok_or(anyhow::anyhow!("Found non UTF-8 characters in filename"))?;

if !file_name.ends_with(".conf") {
if !(file_name.starts_with(BLS_ENTRY_PREFIX) && file_name.ends_with(".conf")) {
continue;
}

Expand Down Expand Up @@ -1318,8 +1319,16 @@ mod tests {
"loader/entries/random_file.txt",
"Random file that we won't parse",
)?;
tempdir.atomic_write("loader/entries/entry1.conf", entry1)?;
tempdir.atomic_write("loader/entries/entry2.conf", entry2)?;
tempdir.atomic_write(
"loader/entries/random_file.conf",
"Random file that we won't parse",
)?;
tempdir.atomic_write(
"loader/entries/bootc_random_file.txt",
"Random file that we won't parse",
)?;
tempdir.atomic_write("loader/entries/bootc_entry1.conf", entry1)?;
tempdir.atomic_write("loader/entries/bootc_entry2.conf", entry2)?;

let result =
get_sorted_type1_boot_entries_helper(&tempdir, true, false, Bootloader::Systemd)
Expand Down Expand Up @@ -1635,8 +1644,8 @@ mod tests {

tempdir.create_dir_all("loader/entries")?;
tempdir.create_dir_all("loader/entries.staged")?;
tempdir.atomic_write("loader/entries/active.conf", active_entry)?;
tempdir.atomic_write("loader/entries.staged/staged.conf", staged_entry)?;
tempdir.atomic_write("loader/entries/bootc_active.conf", active_entry)?;
tempdir.atomic_write("loader/entries.staged/bootc_staged.conf", staged_entry)?;

let result = list_type1_entries(&tempdir)?;
assert_eq!(result.len(), 2);
Expand Down Expand Up @@ -1673,7 +1682,7 @@ mod tests {

let entry2 = format!(
r#"
title Fedora Linux (2.0.0)
title Fedora Linux (2.0.0)
version 2.0.0
sort-key {}
linux /boot/vmlinuz
Expand Down
15 changes: 15 additions & 0 deletions crates/lib/src/bootc_composefs/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use crate::{
boot::{BOOTC_UKI_DIR, compute_boot_digest_uki, get_uki_name},
state::update_boot_digest_in_origin,
},
composefs_consts::{BLS_ENTRY_PREFIX, TYPE1_ENT_PATH},
store::Storage,
};
use anyhow::Result;
use cap_std_ext::cap_std::fs::Dir;
use composefs_ctl::composefs_boot;
use fn_error_context::context;
use linux_kernel_cmdline::utf8::Cmdline;
Expand Down Expand Up @@ -52,3 +54,16 @@ pub(crate) fn get_uki_cmdline(

return Ok(Cmdline::from(cmdline.to_owned()));
}

#[context("Staging non-managed BLS entry")]
pub(crate) fn stage_non_manged_bls_entries(boot_dir: &Dir, staged_entries: &Dir) -> Result<()> {
let original_entries = boot_dir.open_dir(TYPE1_ENT_PATH)?;
for entry in original_entries.entries_utf8()? {
let entry = entry?;
let entry_name = entry.file_name()?;
if entry.file_type()?.is_file() && !entry_name.starts_with(BLS_ENTRY_PREFIX) {
original_entries.copy(entry_name.clone(), staged_entries, entry_name)?;
}
}
Ok(())
}
Loading
Loading