1
0
mirror of https://github.com/containers/bootc.git synced 2026-02-05 06:45:13 +01:00

system-reinstall-bootc: Add context annotations to Result-returning functions

Add #[context()] attribute macro to all functions that return Result
to improve error reporting. This includes adding the fn-error-context
dependency and importing the context macro in all relevant modules.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: John Eckersberg <jeckersb@redhat.com>
This commit is contained in:
John Eckersberg
2025-10-06 12:43:53 -04:00
committed by Colin Walters
parent 4d56384290
commit 82ff6770f2
9 changed files with 33 additions and 0 deletions

1
Cargo.lock generated
View File

@@ -2574,6 +2574,7 @@ dependencies = [
"clap",
"crossterm 0.29.0",
"dialoguer",
"fn-error-context",
"indoc",
"log",
"openssh-keys",

View File

@@ -22,6 +22,7 @@ bootc-utils = { package = "bootc-internal-utils", path = "../utils", version = "
anstream = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
fn-error-context = { workspace = true }
indoc = { workspace = true }
log = { workspace = true }
rustix = { workspace = true }

View File

@@ -1,6 +1,8 @@
use anyhow::Result;
use bootc_mount::Filesystem;
use fn_error_context::context;
#[context("check_root_siblings")]
pub(crate) fn check_root_siblings() -> Result<Vec<String>> {
let mounts = bootc_mount::run_findmnt(&[], None)?;
let problem_filesystems: Vec<String> = mounts

View File

@@ -2,6 +2,7 @@ use std::{fs::File, io::BufReader};
use anyhow::{Context, Result};
use bootc_utils::PathQuotedDisplay;
use fn_error_context::context;
use serde::{Deserialize, Serialize};
mod cli;
@@ -17,6 +18,7 @@ pub(crate) struct ReinstallConfig {
}
impl ReinstallConfig {
#[context("load")]
pub fn load() -> Result<Option<Self>> {
let Some(config) = std::env::var_os(CONFIG_VAR) else {
return Ok(None);

View File

@@ -3,6 +3,7 @@ use std::process::Command;
use anyhow::Result;
use bootc_mount::run_findmnt;
use bootc_utils::{CommandRunExt, ResultExt};
use fn_error_context::context;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
@@ -23,6 +24,7 @@ pub(crate) struct LogicalVolume {
vg_name: String,
}
#[context("parse_volumes")]
pub(crate) fn parse_volumes(group: Option<&str>) -> Result<Vec<LogicalVolume>> {
if which::which("podman").is_err() {
tracing::debug!("lvs binary not found. Skipping logical volume check.");
@@ -46,6 +48,7 @@ pub(crate) fn parse_volumes(group: Option<&str>) -> Result<Vec<LogicalVolume>> {
.collect())
}
#[context("check_root_siblings")]
pub(crate) fn check_root_siblings() -> Result<Vec<String>> {
let all_volumes = parse_volumes(None)?;

View File

@@ -3,6 +3,7 @@
use anyhow::{ensure, Context, Result};
use bootc_utils::CommandRunExt;
use clap::Parser;
use fn_error_context::context;
use rustix::process::getuid;
mod btrfs;
@@ -29,6 +30,7 @@ struct Opts {
// Note if we ever add any other options here,
}
#[context("run")]
fn run() -> Result<()> {
// We historically supported an environment variable providing a config to override the image, so
// keep supporting that. I'm considering deprecating that though.

View File

@@ -3,9 +3,11 @@ use crate::prompt;
use super::ROOT_KEY_MOUNT_POINT;
use anyhow::{ensure, Context, Result};
use bootc_utils::CommandRunExt;
use fn_error_context::context;
use std::process::Command;
use which::which;
#[context("bootc_has_clean")]
fn bootc_has_clean(image: &str) -> Result<bool> {
let output = Command::new("podman")
.args([
@@ -22,6 +24,7 @@ fn bootc_has_clean(image: &str) -> Result<bool> {
Ok(stdout_str.contains("--cleanup"))
}
#[context("reinstall_command")]
pub(crate) fn reinstall_command(image: &str, ssh_key_file: &str) -> Result<Command> {
let mut podman_command_and_args = [
// We use podman to run the bootc container. This might change in the future to remove the
@@ -108,6 +111,7 @@ fn image_exists_command(image: &str) -> Command {
command
}
#[context("pull_if_not_present")]
pub(crate) fn pull_if_not_present(image: &str) -> Result<()> {
let result = image_exists_command(image).status()?;
@@ -136,6 +140,7 @@ const fn podman_install_script_path() -> &'static str {
}
}
#[context("ensure_podman_installed")]
pub(crate) fn ensure_podman_installed() -> Result<()> {
if which("podman").is_ok() {
return Ok(());

View File

@@ -11,6 +11,7 @@ macro_rules! println_flush {
use crate::{btrfs, lvm, prompt, users::get_all_users_keys};
use anyhow::{ensure, Context, Result};
use fn_error_context::context;
use crossterm::event::{self, Event};
use std::time::Duration;
@@ -19,6 +20,7 @@ const NO_SSH_PROMPT: &str = "None of the users on this system found have authori
if your image doesn't use cloud-init or other means to set up users, \
you may not be able to log in after reinstalling. Do you want to continue?";
#[context("prompt_single_user")]
fn prompt_single_user(user: &crate::users::UserKeys) -> Result<Vec<&crate::users::UserKeys>> {
let prompt = indoc::formatdoc! {
"Found only one user ({user}) with {num_keys} SSH authorized keys.
@@ -32,6 +34,7 @@ fn prompt_single_user(user: &crate::users::UserKeys) -> Result<Vec<&crate::users
Ok(if answer { vec![&user] } else { vec![] })
}
#[context("prompt_user_selection")]
fn prompt_user_selection(
all_users: &[crate::users::UserKeys],
) -> Result<Vec<&crate::users::UserKeys>> {
@@ -55,6 +58,7 @@ fn prompt_user_selection(
.collect())
}
#[context("reboot")]
pub(crate) fn reboot() -> Result<()> {
let delay_seconds = 10;
println_flush!(
@@ -79,6 +83,7 @@ pub(crate) fn reboot() -> Result<()> {
/// Temporary safety mechanism to stop devs from running it on their dev machine. TODO: Discuss
/// final prompting UX in https://github.com/bootc-dev/bootc/discussions/1060
#[context("temporary_developer_protection_prompt")]
pub(crate) fn temporary_developer_protection_prompt() -> Result<()> {
// Print an empty line so that the warning stands out from the rest of the output
println_flush!();
@@ -94,6 +99,7 @@ pub(crate) fn temporary_developer_protection_prompt() -> Result<()> {
Ok(())
}
#[context("ask_yes_no")]
pub(crate) fn ask_yes_no(prompt: &str, default: bool) -> Result<bool> {
dialoguer::Confirm::new()
.with_prompt(prompt)
@@ -114,6 +120,7 @@ pub(crate) fn press_enter() {
}
}
#[context("mount_warning")]
pub(crate) fn mount_warning() -> Result<()> {
let mut mounts = btrfs::check_root_siblings()?;
mounts.extend(lvm::check_root_siblings()?);
@@ -138,6 +145,7 @@ pub(crate) fn mount_warning() -> Result<()> {
/// The keys are stored in a temporary file which is passed to
/// the podman run invocation to be used by
/// `bootc install to-existing-root --root-ssh-authorized-keys`
#[context("get_ssh_keys")]
pub(crate) fn get_ssh_keys(temp_key_file_path: &str) -> Result<()> {
let users = get_all_users_keys()?;
if users.is_empty() {

View File

@@ -1,6 +1,7 @@
use anyhow::{Context, Result};
use bootc_utils::CommandRunExt;
use bootc_utils::PathQuotedDisplay;
use fn_error_context::context;
use openssh_keys::PublicKey;
use rustix::fs::Uid;
use rustix::process::geteuid;
@@ -17,6 +18,7 @@ use std::os::unix::process::CommandExt;
use std::process::Command;
use uzers::os::unix::UserExt;
#[context("loginctl_users")]
fn loginctl_users() -> Result<BTreeSet<String>> {
let loginctl_raw_output = loginctl_run_compat()?;
@@ -24,6 +26,7 @@ fn loginctl_users() -> Result<BTreeSet<String>> {
}
/// See [`test::test_parse_lsblk`] for example loginctl output
#[context("loginctl_parse")]
fn loginctl_parse(users: Value) -> Result<BTreeSet<String>> {
users
.as_array()
@@ -47,6 +50,7 @@ fn loginctl_parse(users: Value) -> Result<BTreeSet<String>> {
}
/// Run `loginctl` with some compatibility maneuvers to get JSON output
#[context("loginctl_run_compat")]
fn loginctl_run_compat() -> Result<Value> {
let mut command = Command::new("loginctl");
command.arg("list-sessions").arg("--output").arg("json");
@@ -71,6 +75,7 @@ struct UidChange {
}
impl UidChange {
#[context("new")]
fn new(change_to_uid: Uid) -> Result<Self> {
let (uid, euid) = (getuid(), geteuid());
set_thread_res_uid(uid, change_to_uid, euid).context("setting effective uid failed")?;
@@ -115,6 +120,7 @@ struct SshdConfig<'a> {
}
impl<'a> SshdConfig<'a> {
#[context("parse")]
pub fn parse(sshd_output: &'a str) -> Result<SshdConfig<'a>> {
let config = sshd_output
.lines()
@@ -138,6 +144,7 @@ impl<'a> SshdConfig<'a> {
}
}
#[context("get_keys_from_files")]
fn get_keys_from_files(user: &uzers::User, keyfiles: &Vec<&str>) -> Result<Vec<PublicKey>> {
let home_dir = user.home_dir();
let mut user_authorized_keys: Vec<PublicKey> = Vec::new();
@@ -170,6 +177,7 @@ fn get_keys_from_files(user: &uzers::User, keyfiles: &Vec<&str>) -> Result<Vec<P
Ok(user_authorized_keys)
}
#[context("get_keys_from_command")]
fn get_keys_from_command(command: &str, command_user: &str) -> Result<Vec<PublicKey>> {
let user_config = uzers::get_user_by_name(command_user).context(format!(
"authorized_keys_command_user {command_user} not found"
@@ -184,6 +192,7 @@ fn get_keys_from_command(command: &str, command_user: &str) -> Result<Vec<Public
Ok(keys)
}
#[context("get_all_users_keys")]
pub(crate) fn get_all_users_keys() -> Result<Vec<UserKeys>> {
let loginctl_user_names = loginctl_users().context("enumerate users")?;