diff --git a/.bootc.metadata b/.bootc.metadata index 8b50d90..5f481cf 100644 --- a/.bootc.metadata +++ b/.bootc.metadata @@ -1,2 +1,2 @@ -f1b1bc4ec3e566de914724b4e9285d3068a5ecf7 SOURCES/bootc-0.1.9-vendor.tar.zstd -0be0692496faa3b690fe5a7402cbde4b49985b0f SOURCES/bootc-0.1.9.tar.zstd +991b2e05a2bb38d011e1d15a1bd6841e3ca8ad5a SOURCES/bootc-0.1.14-vendor.tar.zstd +8aa9c8103f0eed46b9b59747021c6ccdc9e67f75 SOURCES/bootc-0.1.14.tar.zstd diff --git a/.gitignore b/.gitignore index 824eefe..5ce363f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -SOURCES/bootc-0.1.9-vendor.tar.zstd -SOURCES/bootc-0.1.9.tar.zstd +SOURCES/bootc-0.1.14-vendor.tar.zstd +SOURCES/bootc-0.1.14.tar.zstd diff --git a/SOURCES/0001-Add-a-rollback-verb-and-rollbackQueued-status.patch b/SOURCES/0001-Add-a-rollback-verb-and-rollbackQueued-status.patch deleted file mode 100644 index b96ac6c..0000000 --- a/SOURCES/0001-Add-a-rollback-verb-and-rollbackQueued-status.patch +++ /dev/null @@ -1,321 +0,0 @@ -From 04baad3080246b1ce26df7a783874b2b1b3ddaa4 Mon Sep 17 00:00:00 2001 -From: Colin Walters -Date: Sat, 9 Mar 2024 15:33:27 -0500 -Subject: [PATCH] Add a `rollback` verb and `rollbackQueued` status - -I'd really hoped to do something more declarative here, and -really flesh out the intersections with automated upgrades -and automated rollbacks. - -But, this just exposes the simple primitive, equivalent -to `rpm-ostree rollback`. - -Signed-off-by: Colin Walters ---- - lib/src/cli.rs | 33 ++++++++++++ - lib/src/deploy.rs | 61 ++++++++++++++++++++++- - lib/src/spec.rs | 40 +++++++++++++++ - lib/src/status.rs | 15 +++++- - tests/integration/playbooks/rollback.yaml | 4 +- - 5 files changed, 148 insertions(+), 5 deletions(-) - -diff --git a/lib/src/cli.rs b/lib/src/cli.rs -index 42bfbcc..623796a 100644 ---- a/lib/src/cli.rs -+++ b/lib/src/cli.rs -@@ -89,6 +89,10 @@ pub(crate) struct SwitchOpts { - pub(crate) target: String, - } - -+/// Options controlling rollback -+#[derive(Debug, Parser, PartialEq, Eq)] -+pub(crate) struct RollbackOpts {} -+ - /// Perform an edit operation - #[derive(Debug, Parser, PartialEq, Eq)] - pub(crate) struct EditOpts { -@@ -214,6 +218,18 @@ pub(crate) enum Opt { - /// This operates in a very similar fashion to `upgrade`, but changes the container image reference - /// instead. - Switch(SwitchOpts), -+ /// Change the bootloader entry ordering; the deployment under `rollback` will be queued for the next boot, -+ /// and the current will become rollback. If there is a `staged` entry (an unapplied, queued upgrade) -+ /// then it will be discarded. -+ /// -+ /// Note that absent any additional control logic, if there is an active agent doing automated upgrades -+ /// (such as the default `bootc-fetch-apply-updates.timer` and associated `.service`) the -+ /// change here may be reverted. It's recommended to only use this in concert with an agent that -+ /// is in active control. -+ /// -+ /// A systemd journal message will be logged with `MESSAGE_ID=26f3b1eb24464d12aa5e7b544a6b5468` in -+ /// order to detect a rollback invocation. -+ Rollback(RollbackOpts), - /// Apply full changes to the host specification. - /// - /// This command operates very similarly to `kubectl apply`; if invoked interactively, -@@ -500,6 +516,14 @@ async fn switch(opts: SwitchOpts) -> Result<()> { - Ok(()) - } - -+/// Implementation of the `bootc rollback` CLI command. -+#[context("Rollback")] -+async fn rollback(_opts: RollbackOpts) -> Result<()> { -+ prepare_for_write().await?; -+ let sysroot = &get_locked_sysroot().await?; -+ crate::deploy::rollback(sysroot).await -+} -+ - /// Implementation of the `bootc edit` CLI command. - #[context("Editing spec")] - async fn edit(opts: EditOpts) -> Result<()> { -@@ -522,7 +546,15 @@ async fn edit(opts: EditOpts) -> Result<()> { - println!("Edit cancelled, no changes made."); - return Ok(()); - } -+ host.spec.verify_transition(&new_host.spec)?; - let new_spec = RequiredHostSpec::from_spec(&new_host.spec)?; -+ -+ // We only support two state transitions right now; switching the image, -+ // or flipping the bootloader ordering. -+ if host.spec.boot_order != new_host.spec.boot_order { -+ return crate::deploy::rollback(sysroot).await; -+ } -+ - let fetched = crate::deploy::pull(sysroot, new_spec.image, opts.quiet).await?; - - // TODO gc old layers here -@@ -586,6 +618,7 @@ async fn run_from_opt(opt: Opt) -> Result<()> { - match opt { - Opt::Upgrade(opts) => upgrade(opts).await, - Opt::Switch(opts) => switch(opts).await, -+ Opt::Rollback(opts) => rollback(opts).await, - Opt::Edit(opts) => edit(opts).await, - Opt::UsrOverlay => usroverlay().await, - #[cfg(feature = "install")] -diff --git a/lib/src/deploy.rs b/lib/src/deploy.rs -index 444ad8e..3eb31a8 100644 ---- a/lib/src/deploy.rs -+++ b/lib/src/deploy.rs -@@ -5,7 +5,7 @@ - use std::io::{BufRead, Write}; - - use anyhow::Ok; --use anyhow::{Context, Result}; -+use anyhow::{anyhow, Context, Result}; - - use cap_std::fs::{Dir, MetadataExt}; - use cap_std_ext::cap_std; -@@ -19,8 +19,8 @@ use ostree_ext::ostree; - use ostree_ext::ostree::Deployment; - use ostree_ext::sysroot::SysrootLock; - --use crate::spec::HostSpec; - use crate::spec::ImageReference; -+use crate::spec::{BootOrder, HostSpec}; - use crate::status::labels_of_config; - - // TODO use https://github.com/ostreedev/ostree-rs-ext/pull/493/commits/afc1837ff383681b947de30c0cefc70080a4f87a -@@ -276,6 +276,63 @@ pub(crate) async fn stage( - Ok(()) - } - -+/// Implementation of rollback functionality -+pub(crate) async fn rollback(sysroot: &SysrootLock) -> Result<()> { -+ const ROLLBACK_JOURNAL_ID: &str = "26f3b1eb24464d12aa5e7b544a6b5468"; -+ let repo = &sysroot.repo(); -+ let (booted_deployment, deployments, host) = crate::status::get_status_require_booted(sysroot)?; -+ -+ let new_spec = { -+ let mut new_spec = host.spec.clone(); -+ new_spec.boot_order = new_spec.boot_order.swap(); -+ new_spec -+ }; -+ -+ // Just to be sure -+ host.spec.verify_transition(&new_spec)?; -+ -+ let reverting = new_spec.boot_order == BootOrder::Default; -+ if reverting { -+ println!("notice: Reverting queued rollback state"); -+ } -+ let rollback_status = host -+ .status -+ .rollback -+ .ok_or_else(|| anyhow!("No rollback available"))?; -+ let rollback_image = rollback_status -+ .query_image(repo)? -+ .ok_or_else(|| anyhow!("Rollback is not container image based"))?; -+ let msg = format!("Rolling back to image: {}", rollback_image.manifest_digest); -+ libsystemd::logging::journal_send( -+ libsystemd::logging::Priority::Info, -+ &msg, -+ [ -+ ("MESSAGE_ID", ROLLBACK_JOURNAL_ID), -+ ("BOOTC_MANIFEST_DIGEST", &rollback_image.manifest_digest), -+ ] -+ .into_iter(), -+ )?; -+ // SAFETY: If there's a rollback status, then there's a deployment -+ let rollback_deployment = deployments.rollback.expect("rollback deployment"); -+ let new_deployments = if reverting { -+ [booted_deployment, rollback_deployment] -+ } else { -+ [rollback_deployment, booted_deployment] -+ }; -+ let new_deployments = new_deployments -+ .into_iter() -+ .chain(deployments.other) -+ .collect::>(); -+ tracing::debug!("Writing new deployments: {new_deployments:?}"); -+ sysroot.write_deployments(&new_deployments, gio::Cancellable::NONE)?; -+ if reverting { -+ println!("Next boot: current deployment"); -+ } else { -+ println!("Next boot: rollback deployment"); -+ } -+ Ok(()) -+} -+ - fn find_newest_deployment_name(deploysdir: &Dir) -> Result { - let mut dirs = Vec::new(); - for ent in deploysdir.entries()? { -diff --git a/lib/src/spec.rs b/lib/src/spec.rs -index 6de9639..5f6df93 100644 ---- a/lib/src/spec.rs -+++ b/lib/src/spec.rs -@@ -28,12 +28,27 @@ pub struct Host { - pub status: HostStatus, - } - -+/// Configuration for system boot ordering. -+ -+#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] -+#[serde(rename_all = "camelCase")] -+pub enum BootOrder { -+ /// The staged or booted deployment will be booted next -+ #[default] -+ Default, -+ /// The rollback deployment will be booted next -+ Rollback, -+} -+ - #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] - #[serde(rename_all = "camelCase")] - /// The host specification - pub struct HostSpec { - /// The host image - pub image: Option, -+ /// If set, and there is a rollback deployment, it will be set for the next boot. -+ #[serde(default)] -+ pub boot_order: BootOrder, - } - - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -@@ -121,6 +136,9 @@ pub struct HostStatus { - pub booted: Option, - /// The previously booted image - pub rollback: Option, -+ /// Set to true if the rollback entry is queued for the next boot. -+ #[serde(default)] -+ pub rollback_queued: bool, - - /// The detected type of system - #[serde(rename = "type")] -@@ -152,6 +170,28 @@ impl Default for Host { - } - } - -+impl HostSpec { -+ /// Validate a spec state transition; some changes cannot be made simultaneously, -+ /// such as fetching a new image and doing a rollback. -+ pub(crate) fn verify_transition(&self, new: &Self) -> anyhow::Result<()> { -+ let rollback = self.boot_order != new.boot_order; -+ let image_change = self.image != new.image; -+ if rollback && image_change { -+ anyhow::bail!("Invalid state transition: rollback and image change"); -+ } -+ Ok(()) -+ } -+} -+ -+impl BootOrder { -+ pub(crate) fn swap(&self) -> Self { -+ match self { -+ BootOrder::Default => BootOrder::Rollback, -+ BootOrder::Rollback => BootOrder::Default, -+ } -+ } -+} -+ - impl Display for ImageReference { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // For the default of fetching from a remote registry, just output the image name -diff --git a/lib/src/status.rs b/lib/src/status.rs -index dba4889..e8f1fa5 100644 ---- a/lib/src/status.rs -+++ b/lib/src/status.rs -@@ -1,6 +1,6 @@ - use std::collections::VecDeque; - --use crate::spec::{BootEntry, Host, HostSpec, HostStatus, HostType, ImageStatus}; -+use crate::spec::{BootEntry, BootOrder, Host, HostSpec, HostStatus, HostType, ImageStatus}; - use crate::spec::{ImageReference, ImageSignature}; - use anyhow::{Context, Result}; - use camino::Utf8Path; -@@ -224,11 +224,22 @@ pub(crate) fn get_status( - .iter() - .position(|d| d.is_staged()) - .map(|i| related_deployments.remove(i).unwrap()); -+ tracing::debug!("Staged: {staged:?}"); - // Filter out the booted, the caller already found that - if let Some(booted) = booted_deployment.as_ref() { - related_deployments.retain(|f| !f.equal(booted)); - } - let rollback = related_deployments.pop_front(); -+ let rollback_queued = match (booted_deployment.as_ref(), rollback.as_ref()) { -+ (Some(booted), Some(rollback)) => rollback.index() < booted.index(), -+ _ => false, -+ }; -+ let boot_order = if rollback_queued { -+ BootOrder::Rollback -+ } else { -+ BootOrder::Default -+ }; -+ tracing::debug!("Rollback queued={rollback_queued:?}"); - let other = { - related_deployments.extend(other_deployments); - related_deployments -@@ -262,6 +273,7 @@ pub(crate) fn get_status( - .and_then(|entry| entry.image.as_ref()) - .map(|img| HostSpec { - image: Some(img.image.clone()), -+ boot_order, - }) - .unwrap_or_default(); - -@@ -281,6 +293,7 @@ pub(crate) fn get_status( - staged, - booted, - rollback, -+ rollback_queued, - ty, - }; - Ok((deployments, host)) -diff --git a/tests/integration/playbooks/rollback.yaml b/tests/integration/playbooks/rollback.yaml -index a801656..e193ff5 100644 ---- a/tests/integration/playbooks/rollback.yaml -+++ b/tests/integration/playbooks/rollback.yaml -@@ -6,8 +6,8 @@ - failed_counter: "0" - - tasks: -- - name: rpm-ostree rollback -- command: rpm-ostree rollback -+ - name: bootc rollback -+ command: bootc rollback - become: true - - - name: Reboot to deploy new system --- -2.43.0 - diff --git a/SPECS/bootc.spec b/SPECS/bootc.spec index 2b79712..c1469c4 100644 --- a/SPECS/bootc.spec +++ b/SPECS/bootc.spec @@ -1,8 +1,8 @@ %bcond_without check Name: bootc -Version: 0.1.9 -Release: 3%{?dist} +Version: 0.1.14 +Release: 1%{?dist} Summary: Bootable container system # Apache-2.0 @@ -18,11 +18,10 @@ URL: https://github.com/containers/bootc Source0: %{url}/releases/download/v%{version}/bootc-%{version}.tar.zstd Source1: %{url}/releases/download/v%{version}/bootc-%{version}-vendor.tar.zstd -Patch0: 0001-Add-a-rollback-verb-and-rollbackQueued-status.patch - # https://fedoraproject.org/wiki/Changes/EncourageI686LeafRemoval ExcludeArch: %{ix86} +BuildRequires: libzstd-devel BuildRequires: make BuildRequires: ostree-devel BuildRequires: openssl-devel @@ -83,6 +82,10 @@ Recommends: bootupd %changelog +* Mon Jul 29 2024 Joseph Marrero - 0.1.14-1 +- Update to 0.1.14 + Resolves: #RHEL-50683, #RHEL-50684, #RHEL-50685 + * Thu Mar 28 2024 Colin Walters - 0.1.9-3 - Backport rollback Resolves: #RHEL-30879