From 49b6d9b9ecc32b9da2ea07e04353716496f71790 Mon Sep 17 00:00:00 2001 From: Brian Telfer Date: Tue, 2 Jun 2026 17:39:55 -0700 Subject: [PATCH 1/3] engineering: Add AZL4 distro detection and extend GRUB update path Implements AzureLinuxRelease::AzL4 variant, VERSION_ID 4.x parsing, ID_LIKE=fedora matching, updated GRUB match arms for AzL3|AzL4, and image_distro() fallback to host os-release. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/osutils/src/mkinitrd.rs | 2 + crates/osutils/src/osrelease.rs | 47 +++++++++++++++++++++++ crates/osutils/src/testutils/osrelease.rs | 25 ++++++++++++ crates/trident/src/engine/boot/grub.rs | 9 +++-- crates/trident/src/engine/context/mod.rs | 10 ++++- 5 files changed, 88 insertions(+), 5 deletions(-) diff --git a/crates/osutils/src/mkinitrd.rs b/crates/osutils/src/mkinitrd.rs index c6ab3d2e1..d01831826 100644 --- a/crates/osutils/src/mkinitrd.rs +++ b/crates/osutils/src/mkinitrd.rs @@ -118,6 +118,8 @@ mod functional_test { fn test_regenerate_initrd() { let pattern = if osrelease::is_azl3().unwrap() { "/boot/initramfs-*.azl3.img" + } else if osrelease::is_azl4().unwrap() { + "/boot/initramfs-*.azl4.img" } else { "/boot/initrd.img-*" }; diff --git a/crates/osutils/src/osrelease.rs b/crates/osutils/src/osrelease.rs index e51926e74..c39981c6f 100644 --- a/crates/osutils/src/osrelease.rs +++ b/crates/osutils/src/osrelease.rs @@ -31,6 +31,11 @@ pub fn is_azl3() -> Result { Ok(OsRelease::read()?.get_distro().is_azl3()) } +/// Returns whether the host is running Azure Linux 4. +pub fn is_azl4() -> Result { + Ok(OsRelease::read()?.get_distro().is_azl4()) +} + /// Represents the contents of the /etc/os-release file. /// /// See @@ -146,6 +151,8 @@ impl OsRelease { AzureLinuxRelease::AzL2 } else if v.starts_with("3.") { AzureLinuxRelease::AzL3 + } else if v.starts_with("4.") { + AzureLinuxRelease::AzL4 } else { trace!("Unknown Azure Linux release: {v}"); AzureLinuxRelease::Other @@ -342,6 +349,10 @@ impl Distro { self == &Distro::AzureLinux(AzureLinuxRelease::AzL3) } + pub fn is_azl4(&self) -> bool { + self == &Distro::AzureLinux(AzureLinuxRelease::AzL4) + } + pub fn is_acl(&self) -> bool { self == &Distro::AzureContainerLinux } @@ -354,6 +365,7 @@ pub enum AzureLinuxRelease { Other, AzL2, AzL3, + AzL4, } #[cfg(test)] @@ -429,6 +441,41 @@ mod tests { ); } + #[test] + fn test_parse_azl4() { + let data = indoc::indoc! { + r#" + NAME="Azure Linux" + VERSION="4.0 (Four Alpha2)" + RELEASE_TYPE=development + ID=azurelinux + ID_LIKE=fedora + VERSION_ID="4.0" + VERSION_CODENAME="" + PRETTY_NAME="Azure Linux 4.0 (Four Alpha2)" + ANSI_COLOR="0;38;2;60;110;180" + LOGO=azurelinux-logo-icon + CPE_NAME="cpe:/o:azurelinuxproject:azurelinux:4.0" + DEFAULT_HOSTNAME="azurelinux" + HOME_URL="https://aka.ms/azurelinux" + DOCUMENTATION_URL="https://aka.ms/azurelinux" + SUPPORT_URL="https://aka.ms/azurelinux" + BUG_REPORT_URL="https://aka.ms/azurelinux" + SUPPORT_END=2026-05-15 + "#, + }; + + let os_release = OsRelease::parse(data); + assert_eq!(os_release.id, Some("azurelinux".to_string())); + assert_eq!(os_release.version_id, Some("4.0".to_string())); + assert_eq!(os_release.id_like, Some("fedora".to_string())); + assert_eq!(os_release.release_type, Some("development".to_string())); + assert_eq!( + os_release.get_distro(), + Distro::AzureLinux(AzureLinuxRelease::AzL4) + ); + } + #[test] fn test_parse_extension_release() { let data = indoc::indoc! { diff --git a/crates/osutils/src/testutils/osrelease.rs b/crates/osutils/src/testutils/osrelease.rs index 6feff02bc..27a2e5b17 100644 --- a/crates/osutils/src/testutils/osrelease.rs +++ b/crates/osutils/src/testutils/osrelease.rs @@ -38,11 +38,36 @@ const AZURE_LINUX_3_OS_RELEASE: &str = indoc::indoc! { "#, }; +/// Azure Linux 4.0 sample os-release file. +const AZURE_LINUX_4_OS_RELEASE: &str = indoc::indoc! { + r#" + NAME="Azure Linux" + VERSION="4.0 (Cloud Variant Beta)" + RELEASE_TYPE=development + ID=azurelinux + ID_LIKE=fedora + VERSION_ID="4.0" + VERSION_CODENAME="" + PRETTY_NAME="Azure Linux 4.0 (Cloud Variant Beta)" + ANSI_COLOR="0;38;2;60;110;180" + LOGO=azurelinux-logo-icon + CPE_NAME="cpe:/o:azurelinuxproject:azurelinux:4.0" + DEFAULT_HOSTNAME="azurelinux" + HOME_URL="https://aka.ms/azurelinux" + DOCUMENTATION_URL="https://aka.ms/azurelinux" + SUPPORT_URL="https://aka.ms/azurelinux" + BUG_REPORT_URL="https://aka.ms/azurelinux" + VARIANT="Cloud Variant" + VARIANT_ID=cloud + "#, +}; + /// Creates a mock /etc/os-release file with the given Azure Linux release. pub fn make_mock_os_release(root_path: &Path, azl_release: AzureLinuxRelease) -> Result<(), Error> { let os_release_content = match azl_release { AzureLinuxRelease::AzL2 => AZURE_LINUX_2_OS_RELEASE, AzureLinuxRelease::AzL3 => AZURE_LINUX_3_OS_RELEASE, + AzureLinuxRelease::AzL4 => AZURE_LINUX_4_OS_RELEASE, AzureLinuxRelease::Other => bail!("Unsupported Azure Linux release 'other'"), }; diff --git a/crates/trident/src/engine/boot/grub.rs b/crates/trident/src/engine/boot/grub.rs index b345f5c31..fb25b59c8 100644 --- a/crates/trident/src/engine/boot/grub.rs +++ b/crates/trident/src/engine/boot/grub.rs @@ -63,9 +63,10 @@ pub(super) fn update_configs(ctx: &EngineContext) -> Result<(), Error> { let boot_grub_config_path = Path::new(ROOT_MOUNT_POINT_PATH).join(GRUB2_CONFIG_RELATIVE_PATH); // Update GRUB config on the boot device (volume holding /boot) - match ctx.host_os_release.get_distro() { - Distro::AzureLinux(AzureLinuxRelease::AzL3) => { - update_grub_config_azl3(ctx, &root_device_path, &boot_grub_config_path)?; + // Use the *image* distro (the OS being installed), not the host (MOS ISO). + match ctx.image_distro() { + Distro::AzureLinux(AzureLinuxRelease::AzL3 | AzureLinuxRelease::AzL4) => { + update_grub_config(ctx, &root_device_path, &boot_grub_config_path)?; } d => bail!("Unsupported distro for GRUB config update: {d:?}"), @@ -86,7 +87,7 @@ pub(super) fn update_configs(ctx: &EngineContext) -> Result<(), Error> { } /// Updates the GRUB config for Azure Linux 3.0 using OS modifier. -fn update_grub_config_azl3( +fn update_grub_config( ctx: &EngineContext, root_device_path: &Path, boot_grub_config_path: &Path, diff --git a/crates/trident/src/engine/context/mod.rs b/crates/trident/src/engine/context/mod.rs index 73fe61f4e..f873ff947 100644 --- a/crates/trident/src/engine/context/mod.rs +++ b/crates/trident/src/engine/context/mod.rs @@ -441,8 +441,16 @@ impl EngineContext { } /// Retrieves the distribution of the OS image. + /// + /// Prefers the image's own os-release (e.g., from the COSI being installed). + /// Falls back to the host os-release when no image is available (functional + /// tests, runtime operations outside an install flow). pub(crate) fn image_distro(&self) -> Distro { - self.image_os_release().get_distro() + let distro = self.image_os_release().get_distro(); + match distro { + Distro::Other => self.host_os_release.get_distro(), + d => d, + } } } From cca2a4c31ace91190e67af498178ede67b16a655 Mon Sep 17 00:00:00 2001 From: Brian Telfer Date: Wed, 3 Jun 2026 16:41:07 -0700 Subject: [PATCH 2/3] fix: Only fall back to host distro when no image is mounted image_distro() was falling back to the host os-release whenever the image's distro was Distro::Other. This silently masked unrecognized distros as the host distro, causing GRUB config to be written for the wrong OS. Now: if an image is mounted (self.image.is_some()), always use the image's distro. Fallback to host only fires when no image is present at all (functional tests, runtime operations). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/trident/src/engine/context/mod.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/trident/src/engine/context/mod.rs b/crates/trident/src/engine/context/mod.rs index f873ff947..4632acabc 100644 --- a/crates/trident/src/engine/context/mod.rs +++ b/crates/trident/src/engine/context/mod.rs @@ -443,13 +443,17 @@ impl EngineContext { /// Retrieves the distribution of the OS image. /// /// Prefers the image's own os-release (e.g., from the COSI being installed). - /// Falls back to the host os-release when no image is available (functional - /// tests, runtime operations outside an install flow). + /// Falls back to the host os-release only when no image is mounted + /// (functional tests, runtime operations outside an install flow). + /// + /// If an image IS present but its distro is unrecognized, the image's + /// distro is returned as-is (Distro::Other) so callers can bail + /// explicitly rather than silently using the host's distro. pub(crate) fn image_distro(&self) -> Distro { - let distro = self.image_os_release().get_distro(); - match distro { - Distro::Other => self.host_os_release.get_distro(), - d => d, + if self.image.is_some() { + self.image_os_release().get_distro() + } else { + self.host_os_release.get_distro() } } } From d16a8b4d430e5805dab621d787918093db44650d Mon Sep 17 00:00:00 2001 From: Brian Telfer Date: Thu, 4 Jun 2026 16:11:44 -0700 Subject: [PATCH 3/3] fix: Update grub.rs doc comment and logs for AZL3+AZL4 The function now serves both AZL3 and AZL4. Remove version-specific references from the doc comment and debug log messages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/trident/src/engine/boot/grub.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/trident/src/engine/boot/grub.rs b/crates/trident/src/engine/boot/grub.rs index fb25b59c8..791762239 100644 --- a/crates/trident/src/engine/boot/grub.rs +++ b/crates/trident/src/engine/boot/grub.rs @@ -86,13 +86,13 @@ pub(super) fn update_configs(ctx: &EngineContext) -> Result<(), Error> { )) } -/// Updates the GRUB config for Azure Linux 3.0 using OS modifier. +/// Updates the GRUB config for Azure Linux using OS modifier. fn update_grub_config( ctx: &EngineContext, root_device_path: &Path, boot_grub_config_path: &Path, ) -> Result<(), Error> { - // For azl 3.0, we need to disable cloud-init's network configuration when Trident is + // For azl 3.0+, we need to disable cloud-init's network configuration when Trident is // configuring the network. This is done by setting the 'network-config' kernel parameter // to 'disabled'. if ctx.spec.os.netplan.is_some() { @@ -104,7 +104,7 @@ fn update_grub_config( .context("Failed to disable default cloud-init network config")?; } - debug!("Updating GRUB config for Azure Linux 3.0 with OS modifier"); + debug!("Updating GRUB config with OS modifier"); // OS modifier will read values of verity, selinux, root device, and overlay from original GRUB config // stamp them into /etc/default/grub and regenerate the GRUB config using grub-mkconfig. @@ -223,7 +223,7 @@ fn update_grub_config( osmodifier::modify_boot(&osmod_ctx, &config) .context("Failed to apply boot configuration modifications")?; - debug!("Finished updating GRUB config for Azure Linux 3.0 with OS modifier"); + debug!("Finished updating GRUB config with OS modifier"); Ok(()) }