Skip to content

Commit a507380

Browse files
committed
system-reinstall-bootc: Handle --help
I wanted to add a CLI option here to avoid the reboot, and ran into the fact that our option parsing was suboptimal to start with. We never documented `BOOTC_REINSTALL_CONFIG` at all...I'm kind of tempted to deprecate it. Signed-off-by: Colin Walters <[email protected]>
1 parent 40ebdd5 commit a507380

File tree

3 files changed

+57
-43
lines changed

3 files changed

+57
-43
lines changed
Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,31 @@
1-
use anyhow::{ensure, Context, Result};
2-
use clap::Parser;
1+
use std::{fs::File, io::BufReader};
2+
3+
use anyhow::{Context, Result};
4+
use bootc_utils::PathQuotedDisplay;
35
use serde::{Deserialize, Serialize};
46

57
mod cli;
68

9+
/// The environment variable that can be used to specify an image.
10+
const CONFIG_VAR: &str = "BOOTC_REINSTALL_CONFIG";
11+
712
#[derive(Debug, Deserialize, Serialize)]
813
#[serde(deny_unknown_fields)]
914
pub(crate) struct ReinstallConfig {
1015
/// The bootc image to install on the system.
1116
pub(crate) bootc_image: String,
12-
13-
/// The raw CLI arguments that were used to invoke the program. None if the config was loaded
14-
/// from a file.
15-
#[serde(skip_deserializing)]
16-
cli_flags: Option<Vec<String>>,
1717
}
1818

1919
impl ReinstallConfig {
20-
pub fn parse_from_cli(cli: cli::Cli) -> Self {
21-
Self {
22-
bootc_image: cli.bootc_image,
23-
cli_flags: Some(std::env::args().collect::<Vec<String>>()),
24-
}
25-
}
26-
27-
pub fn load() -> Result<Self> {
28-
Ok(match std::env::var("BOOTC_REINSTALL_CONFIG") {
29-
Ok(config_path) => {
30-
ensure_no_cli_args()?;
31-
32-
serde_yaml::from_slice(
33-
&std::fs::read(&config_path)
34-
.context("reading BOOTC_REINSTALL_CONFIG file {config_path}")?,
35-
)
36-
.context("parsing BOOTC_REINSTALL_CONFIG file {config_path}")?
37-
}
38-
Err(_) => ReinstallConfig::parse_from_cli(cli::Cli::parse()),
39-
})
20+
pub fn load() -> Result<Option<Self>> {
21+
let Some(config) = std::env::var_os(CONFIG_VAR) else {
22+
return Ok(None);
23+
};
24+
let f = File::open(&config)
25+
.with_context(|| format!("Opening {}", PathQuotedDisplay::new(&config)))
26+
.map(BufReader::new)?;
27+
let r = serde_yaml::from_reader(f)
28+
.with_context(|| format!("Parsing config from {}", PathQuotedDisplay::new(&config)))?;
29+
Ok(Some(r))
4030
}
4131
}
42-
43-
fn ensure_no_cli_args() -> Result<()> {
44-
let num_args = std::env::args().len();
45-
46-
ensure!(
47-
num_args == 1,
48-
"BOOTC_REINSTALL_CONFIG is set, but there are {num_args} CLI arguments. BOOTC_REINSTALL_CONFIG is meant to be used with no arguments."
49-
);
50-
51-
Ok(())
52-
}

crates/system-reinstall-bootc/src/main.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use anyhow::{ensure, Context, Result};
44
use bootc_utils::CommandRunExt;
5+
use clap::Parser;
56
use rustix::process::getuid;
67

78
mod btrfs;
@@ -13,19 +14,43 @@ pub(crate) mod users;
1314

1415
const ROOT_KEY_MOUNT_POINT: &str = "/bootc_authorized_ssh_keys/root";
1516

17+
/// Reinstall the system using the provided bootc container.
18+
///
19+
/// This will interactively replace the system with the content of the targeted
20+
/// container image.
21+
///
22+
/// If the environment variable BOOTC_REINSTALL_CONFIG is set, it must be a YAML
23+
/// file with a single member `bootc_image` that specifies the image to install.
24+
/// This will take precedence over the CLI.
25+
#[derive(clap::Parser)]
26+
struct Opts {
27+
/// The bootc image to install
28+
pub(crate) image: String,
29+
// Note if we ever add any other options here,
30+
}
31+
1632
fn run() -> Result<()> {
33+
// We historically supported an environment variable providing a config to override the image, so
34+
// keep supporting that. I'm considering deprecating that though.
35+
let opts = if let Some(config) = config::ReinstallConfig::load().context("loading config")? {
36+
Opts {
37+
image: config.bootc_image,
38+
}
39+
} else {
40+
// Otherwise an image is required.
41+
Opts::parse()
42+
};
43+
1744
bootc_utils::initialize_tracing();
1845
tracing::trace!("starting {}", env!("CARGO_PKG_NAME"));
1946

2047
// Rootless podman is not supported by bootc
2148
ensure!(getuid().is_root(), "Must run as the root user");
2249

23-
let config = config::ReinstallConfig::load().context("loading config")?;
24-
2550
podman::ensure_podman_installed()?;
2651

2752
//pull image early so it can be inspected, e.g. to check for cloud-init
28-
podman::pull_if_not_present(&config.bootc_image)?;
53+
podman::pull_if_not_present(&opts.image)?;
2954

3055
println!();
3156

@@ -41,8 +66,7 @@ fn run() -> Result<()> {
4166

4267
prompt::mount_warning()?;
4368

44-
let mut reinstall_podman_command =
45-
podman::reinstall_command(&config.bootc_image, ssh_key_file_path)?;
69+
let mut reinstall_podman_command = podman::reinstall_command(&opts.image, ssh_key_file_path)?;
4670

4771
println!();
4872
println!("Going to run command:");

crates/tests-integration/src/container.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,24 @@ pub(crate) fn test_bootc_install_config() -> Result<()> {
3939
drop(config);
4040
Ok(())
4141
}
42+
43+
/// Previously system-reinstall-bootc bombed out when run as non-root even if passing --help
44+
fn test_system_reinstall_help() -> Result<()> {
45+
let o = Command::new("runuser")
46+
.args(["-u", "bin", "system-reinstall-bootc", "--help"])
47+
.output()?;
48+
assert!(o.status.success());
49+
Ok(())
50+
}
51+
4252
/// Tests that should be run in a default container image.
4353
#[context("Container tests")]
4454
pub(crate) fn run(testargs: libtest_mimic::Arguments) -> Result<()> {
4555
let tests = [
4656
new_test("bootc upgrade", test_bootc_upgrade),
4757
new_test("install config", test_bootc_install_config),
4858
new_test("status", test_bootc_status),
59+
new_test("system-reinstall --help", test_system_reinstall_help),
4960
];
5061

5162
libtest_mimic::run(&testargs, tests.into()).exit()

0 commit comments

Comments
 (0)