From 722ed77e528cf78cec5046bca8a799b6cbb95889 Mon Sep 17 00:00:00 2001 From: jbtrystram Date: Wed, 15 Jul 2026 15:58:49 +0200 Subject: [PATCH] coreos: generalize to any aleph created by bootc Rename the `coreos` module to `aleph` as `bootc install` will create an aleph file in all cases except the composefs path. There is nothing specific to coreOS anymore in this path. See https://github.com/bootc-dev/bootc/commit/c6112d2d2fa3b785c858741f1585b0578088eda2 And https://github.com/coreos/fedora-coreos-tracker/issues/2187 --- src/aleph.rs | 200 +++++++++++++++++++++++++++++++++++++++++++++++ src/bootupd.rs | 8 +- src/component.rs | 8 +- src/coreos.rs | 123 ----------------------------- src/main.rs | 2 +- 5 files changed, 209 insertions(+), 132 deletions(-) create mode 100644 src/aleph.rs delete mode 100644 src/coreos.rs diff --git a/src/aleph.rs b/src/aleph.rs new file mode 100644 index 00000000..21006a35 --- /dev/null +++ b/src/aleph.rs @@ -0,0 +1,200 @@ +//! Aleph version file support. +//! +//! The "aleph" file records the original OS version at install time. +//! This is written by CoreOS (`.coreos-aleph-version.json`) and +//! bootc (`.bootc-aleph.json`). + +/* + * Copyright (C) 2020 Red Hat, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +use anyhow::{Context, Result}; +use chrono::prelude::*; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::path::Path; + +#[derive(Serialize, Deserialize, Clone, Debug, Hash, Ord, PartialOrd, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +/// See https://github.com/coreos/fedora-coreos-tracker/blob/66d7d00bedd9d5eabc7287b9577f443dcefb7c04/internals/README-internals.md#aleph-version +pub(crate) struct Aleph { + #[serde(alias = "build")] + pub(crate) version: String, +} + +pub(crate) struct AlephWithTimestamp { + pub(crate) aleph: Aleph, + #[allow(dead_code)] + pub(crate) ts: chrono::DateTime, +} + +/// Known aleph file paths, checked in order of priority. +const ALEPH_PATHS: &[&str] = &[ + "sysroot/.coreos-aleph-version.json", + "sysroot/.bootc-aleph.json", +]; + +pub(crate) fn get_aleph_version(root: &Path) -> Result> { + for aleph_path in ALEPH_PATHS { + let path = &root.join(aleph_path); + if !path.exists() { + continue; + } + let statusf = File::open(path).with_context(|| format!("Opening {path:?}"))?; + let meta = statusf.metadata()?; + let bufr = std::io::BufReader::new(statusf); + let aleph: Aleph = serde_json::from_reader(bufr)?; + log::debug!("Found aleph version in {aleph_path}"); + return Ok(Some(AlephWithTimestamp { + aleph, + ts: meta.created()?.into(), + })); + } + Ok(None) +} + +#[cfg(test)] +mod test { + use super::*; + use anyhow::Result; + + const V1_ALEPH_DATA: &str = r##" + { + "version": "32.20201002.dev.2", + "ref": "fedora/x86_64/coreos/testing-devel", + "ostree-commit": "b2ea6159d6274e1bbbb49aa0ef093eda5d53a75c8a793dbe184f760ed64dc862" + }"##; + + // Waiting on https://github.com/rust-lang/rust/pull/125692 + #[cfg(not(target_env = "musl"))] + #[test] + fn test_parse_from_root_empty() -> Result<()> { + // Verify we're a no-op in an empty root + let root: &tempfile::TempDir = &tempfile::tempdir()?; + let root = root.path(); + assert!(get_aleph_version(root).unwrap().is_none()); + Ok(()) + } + + // Waiting on https://github.com/rust-lang/rust/pull/125692 + #[cfg(not(target_env = "musl"))] + #[test] + fn test_parse_from_root_coreos() -> Result<()> { + let root: &tempfile::TempDir = &tempfile::tempdir()?; + let root = root.path(); + let sysroot = &root.join("sysroot"); + std::fs::create_dir(sysroot).context("Creating sysroot")?; + std::fs::write(root.join(ALEPH_PATHS[0]), V1_ALEPH_DATA).context("Writing aleph")?; + let aleph = get_aleph_version(root).unwrap().unwrap(); + assert_eq!(aleph.aleph.version, "32.20201002.dev.2"); + Ok(()) + } + + // Waiting on https://github.com/rust-lang/rust/pull/125692 + #[cfg(not(target_env = "musl"))] + #[test] + fn test_parse_from_root_bootc() -> Result<()> { + let root: &tempfile::TempDir = &tempfile::tempdir()?; + let root = root.path(); + let sysroot = &root.join("sysroot"); + std::fs::create_dir(sysroot).context("Creating sysroot")?; + std::fs::write(root.join(ALEPH_PATHS[1]), V1_ALEPH_DATA).context("Writing aleph")?; + let aleph = get_aleph_version(root).unwrap().unwrap(); + assert_eq!(aleph.aleph.version, "32.20201002.dev.2"); + Ok(()) + } + + // Waiting on https://github.com/rust-lang/rust/pull/125692 + #[cfg(not(target_env = "musl"))] + #[test] + fn test_parse_coreos_preferred_over_bootc() -> Result<()> { + // When both files exist, the CoreOS aleph should be preferred + let root: &tempfile::TempDir = &tempfile::tempdir()?; + let root = root.path(); + let sysroot = &root.join("sysroot"); + std::fs::create_dir(sysroot).context("Creating sysroot")?; + let coreos_data = r##"{"version": "coreos-version"}"##; + let bootc_data = r##"{"version": "bootc-version"}"##; + std::fs::write(root.join(ALEPH_PATHS[0]), coreos_data).context("Writing coreos aleph")?; + std::fs::write(root.join(ALEPH_PATHS[1]), bootc_data).context("Writing bootc aleph")?; + let aleph = get_aleph_version(root).unwrap().unwrap(); + assert_eq!(aleph.aleph.version, "coreos-version"); + Ok(()) + } + + // Waiting on https://github.com/rust-lang/rust/pull/125692 + #[cfg(not(target_env = "musl"))] + #[test] + fn test_parse_from_root_linked() -> Result<()> { + let root: &tempfile::TempDir = &tempfile::tempdir()?; + let root = root.path(); + let sysroot = &root.join("sysroot"); + std::fs::create_dir(sysroot).context("Creating sysroot")?; + let target_name = ".new-ostree-aleph.json"; + let target = &sysroot.join(target_name); + std::fs::write(root.join(target), V1_ALEPH_DATA).context("Writing aleph")?; + std::os::unix::fs::symlink(target_name, root.join(ALEPH_PATHS[0])) + .context("Symlinking")?; + let aleph = get_aleph_version(root).unwrap().unwrap(); + assert_eq!(aleph.aleph.version, "32.20201002.dev.2"); + Ok(()) + } + + #[test] + fn test_parse_old_aleph() -> Result<()> { + // What the aleph file looked like before we changed it in + // https://github.com/osbuild/osbuild/pull/1475 + let alephdata = r##" +{ + "build": "32.20201002.dev.2", + "ref": "fedora/x86_64/coreos/testing-devel", + "ostree-commit": "b2ea6159d6274e1bbbb49aa0ef093eda5d53a75c8a793dbe184f760ed64dc862", + "imgid": "fedora-coreos-32.20201002.dev.2-qemu.x86_64.qcow2" +}"##; + let aleph: Aleph = serde_json::from_str(alephdata)?; + assert_eq!(aleph.version, "32.20201002.dev.2"); + Ok(()) + } + + #[test] + fn test_parse_aleph() -> Result<()> { + let aleph: Aleph = serde_json::from_str(V1_ALEPH_DATA)?; + assert_eq!(aleph.version, "32.20201002.dev.2"); + Ok(()) + } + + #[test] + fn test_parse_bootc_aleph() -> Result<()> { + // A realistic bootc aleph as written by `bootc install`. + // See https://github.com/bootc-dev/bootc/commit/c6112d2d2fa3b785c858741f1585b0578088eda2 + let alephdata = r##" +{ + "digest": "sha256:07bf537cc4e4d208eb0b978f76e5046e55529ce6192b982d8c1a41fa1d61b95a", + "kernel": "6.18.13-200.fc43.x86_64", + "labels": { + "com.coreos.inputhash": "fe9883169714c593d98058606e886b9747710ed15ab1b9cdbd7fa538fb435b3c", + "com.coreos.osname": "fedora-coreos", + "com.coreos.stream": "testing-devel", + "containers.bootc": "1", + "io.buildah.version": "1.42.2", + "org.opencontainers.image.description": "Fedora CoreOS testing-devel", + "org.opencontainers.image.revision": "233fe18749c7d2749581e4307c4cac60967acde4", + "org.opencontainers.image.source": "git@github.com:jbtrystram/fedora-coreos-config.git", + "org.opencontainers.image.title": "Fedora CoreOS testing-devel", + "org.opencontainers.image.version": "43.20260301.20.dev1", + "ostree.bootable": "1", + "ostree.commit": "89635f7cba9de932fc60d71a6bded65ad0db06a35c9d016da03ca7ade9ba4736", + "ostree.final-diffid": "sha256:12787d84fa137cd5649a9005efe98ec9d05ea46245fdc50aecb7dd007f2035b1" + }, + "selinux": "disabled", + "target-image": "ostree-image-signed:docker://quay.io/fedora/fedora-coreos:testing-devel", + "timestamp": null, + "version": "43.20260301.20.dev1" +}"##; + let aleph: Aleph = serde_json::from_str(alephdata)?; + assert_eq!(aleph.version, "43.20260301.20.dev1"); + Ok(()) + } +} diff --git a/src/bootupd.rs b/src/bootupd.rs index 733148e3..6d1a2c92 100644 --- a/src/bootupd.rs +++ b/src/bootupd.rs @@ -4,7 +4,7 @@ use crate::bootloader::{get_bootloader, Bootloader}; use crate::cli::bootupd::InstallOpts; use crate::component::{self, ComponentType}; use crate::component::{Component, ValidationResult}; -use crate::coreos; +use crate::aleph; #[cfg(any( target_arch = "x86_64", target_arch = "aarch64", @@ -598,7 +598,7 @@ pub(crate) fn status() -> Result { // To determine if not-installed components can be adopted: // // `query_adopt_state()` checks for existing installation state, - // such as a `version` in `/sysroot/.coreos-aleph-version.json`, + // such as a `version` in an aleph file (e.g. `.coreos-aleph-version.json` or `.bootc-aleph.json`), // or the presence of `/ostree/deploy`. // // `component.query_adopt()` performs additional checks, @@ -681,8 +681,8 @@ pub(crate) fn print_status(status: &Status) -> Result<()> { } } - if let Some(coreos_aleph) = coreos::get_aleph_version(Path::new("/"))? { - println!("CoreOS aleph version: {}", coreos_aleph.aleph.version); + if let Some(aleph) = aleph::get_aleph_version(Path::new("/"))? { + println!("Aleph version: {}", aleph.aleph.version); } #[cfg(any( diff --git a/src/component.rs b/src/component.rs index 094d5172..23a14b52 100644 --- a/src/component.rs +++ b/src/component.rs @@ -265,10 +265,10 @@ pub(crate) fn write_update_metadata( #[context("Querying adoptable state")] pub(crate) fn query_adopt_state() -> Result> { // This would be extended with support for other operating systems later - if let Some(coreos_aleph) = crate::coreos::get_aleph_version(Path::new("/"))? { + if let Some(aleph) = crate::aleph::get_aleph_version(Path::new("/"))? { let meta = ContentMetadata { - timestamp: coreos_aleph.ts, - version: coreos_aleph.aleph.version, + timestamp: aleph.ts, + version: aleph.aleph.version, versions: None, #[cfg(efi_arch)] default_bootloader: None, @@ -279,7 +279,7 @@ pub(crate) fn query_adopt_state() -> Result> { confident: true, })); } else { - log::trace!("No CoreOS aleph detected"); + log::trace!("No aleph version file detected"); } let ostree_deploy_dir = Path::new("/ostree/deploy"); if ostree_deploy_dir.exists() { diff --git a/src/coreos.rs b/src/coreos.rs deleted file mode 100644 index 8f7aa8d7..00000000 --- a/src/coreos.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Bits specific to Fedora CoreOS (and derivatives). - -/* - * Copyright (C) 2020 Red Hat, Inc. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -use anyhow::{Context, Result}; -use chrono::prelude::*; -use serde::{Deserialize, Serialize}; -use std::fs::File; -use std::path::Path; - -#[derive(Serialize, Deserialize, Clone, Debug, Hash, Ord, PartialOrd, PartialEq, Eq)] -#[serde(rename_all = "kebab-case")] -/// See https://github.com/coreos/fedora-coreos-tracker/blob/66d7d00bedd9d5eabc7287b9577f443dcefb7c04/internals/README-internals.md#aleph-version -pub(crate) struct Aleph { - #[serde(alias = "build")] - pub(crate) version: String, -} - -pub(crate) struct AlephWithTimestamp { - pub(crate) aleph: Aleph, - #[allow(dead_code)] - pub(crate) ts: chrono::DateTime, -} - -/// Path to the file, see above -const ALEPH_PATH: &str = "sysroot/.coreos-aleph-version.json"; - -pub(crate) fn get_aleph_version(root: &Path) -> Result> { - let path = &root.join(ALEPH_PATH); - if !path.exists() { - return Ok(None); - } - let statusf = File::open(path).with_context(|| format!("Opening {path:?}"))?; - let meta = statusf.metadata()?; - let bufr = std::io::BufReader::new(statusf); - let aleph: Aleph = serde_json::from_reader(bufr)?; - Ok(Some(AlephWithTimestamp { - aleph, - ts: meta.created()?.into(), - })) -} - -#[cfg(test)] -mod test { - use super::*; - use anyhow::Result; - - const V1_ALEPH_DATA: &str = r##" - { - "version": "32.20201002.dev.2", - "ref": "fedora/x86_64/coreos/testing-devel", - "ostree-commit": "b2ea6159d6274e1bbbb49aa0ef093eda5d53a75c8a793dbe184f760ed64dc862" - }"##; - - // Waiting on https://github.com/rust-lang/rust/pull/125692 - #[cfg(not(target_env = "musl"))] - #[test] - fn test_parse_from_root_empty() -> Result<()> { - // Verify we're a no-op in an empty root - let root: &tempfile::TempDir = &tempfile::tempdir()?; - let root = root.path(); - assert!(get_aleph_version(root).unwrap().is_none()); - Ok(()) - } - - // Waiting on https://github.com/rust-lang/rust/pull/125692 - #[cfg(not(target_env = "musl"))] - #[test] - fn test_parse_from_root() -> Result<()> { - let root: &tempfile::TempDir = &tempfile::tempdir()?; - let root = root.path(); - let sysroot = &root.join("sysroot"); - std::fs::create_dir(sysroot).context("Creating sysroot")?; - std::fs::write(root.join(ALEPH_PATH), V1_ALEPH_DATA).context("Writing aleph")?; - let aleph = get_aleph_version(root).unwrap().unwrap(); - assert_eq!(aleph.aleph.version, "32.20201002.dev.2"); - Ok(()) - } - - // Waiting on https://github.com/rust-lang/rust/pull/125692 - #[cfg(not(target_env = "musl"))] - #[test] - fn test_parse_from_root_linked() -> Result<()> { - let root: &tempfile::TempDir = &tempfile::tempdir()?; - let root = root.path(); - let sysroot = &root.join("sysroot"); - std::fs::create_dir(sysroot).context("Creating sysroot")?; - let target_name = ".new-ostree-aleph.json"; - let target = &sysroot.join(target_name); - std::fs::write(root.join(target), V1_ALEPH_DATA).context("Writing aleph")?; - std::os::unix::fs::symlink(target_name, root.join(ALEPH_PATH)).context("Symlinking")?; - let aleph = get_aleph_version(root).unwrap().unwrap(); - assert_eq!(aleph.aleph.version, "32.20201002.dev.2"); - Ok(()) - } - - #[test] - fn test_parse_old_aleph() -> Result<()> { - // What the aleph file looked like before we changed it in - // https://github.com/osbuild/osbuild/pull/1475 - let alephdata = r##" -{ - "build": "32.20201002.dev.2", - "ref": "fedora/x86_64/coreos/testing-devel", - "ostree-commit": "b2ea6159d6274e1bbbb49aa0ef093eda5d53a75c8a793dbe184f760ed64dc862", - "imgid": "fedora-coreos-32.20201002.dev.2-qemu.x86_64.qcow2" -}"##; - let aleph: Aleph = serde_json::from_str(alephdata)?; - assert_eq!(aleph.version, "32.20201002.dev.2"); - Ok(()) - } - - #[test] - fn test_parse_aleph() -> Result<()> { - let aleph: Aleph = serde_json::from_str(V1_ALEPH_DATA)?; - assert_eq!(aleph.version, "32.20201002.dev.2"); - Ok(()) - } -} diff --git a/src/main.rs b/src/main.rs index 53371753..fc074c6e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ mod bootloader; mod bootupd; mod cli; mod component; -mod coreos; +mod aleph; #[cfg(any( target_arch = "x86_64", target_arch = "aarch64",