Skip to content

Commit

Permalink
Add a rollback verb and rollbackQueued status
Browse files Browse the repository at this point in the history
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 <walters@verbum.org>
  • Loading branch information
cgwalters committed Mar 26, 2024
1 parent fd94c6f commit 32348a5
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 3 deletions.
25 changes: 25 additions & 0 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<()> {
Expand Down Expand Up @@ -586,6 +610,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")]
Expand Down
49 changes: 48 additions & 1 deletion lib/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -276,6 +276,53 @@ 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 reverting = host.status.rollback_queued;
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::<Vec<_>>();
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<String> {
let mut dirs = Vec::new();
for ent in deploysdir.entries()? {
Expand Down
2 changes: 2 additions & 0 deletions lib/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ pub struct HostStatus {
pub booted: Option<BootEntry>,
/// The previously booted image
pub rollback: Option<BootEntry>,
/// Set to true if the rollback entry is queued for the next boot.
pub rollback_queued: bool,

/// The detected type of system
#[serde(rename = "type")]
Expand Down
7 changes: 7 additions & 0 deletions lib/src/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,17 @@ 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,
};
tracing::debug!("Rollback queued={rollback_queued:?}");
let other = {
related_deployments.extend(other_deployments);
related_deployments
Expand Down Expand Up @@ -281,6 +287,7 @@ pub(crate) fn get_status(
staged,
booted,
rollback,
rollback_queued,
ty,
};
Ok((deployments, host))
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/playbooks/rollback.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 32348a5

Please sign in to comment.