Skip to content

Commit e2541bd

Browse files
committed
utils: add BOOTC_EXP_EXTERNAL_CONTAINER_TOOL env override
Add a single environment variable that allows callers to substitute an alternative binary for both podman and skopeo without creating hard links or symlinks on the filesystem. The _EXP prefix makes clear that this interface is experimental and subject to change. BOOTC_EXP_EXTERNAL_CONTAINER_TOOL defaults to the conventional tool name ("podman" or "skopeo") when unset, preserving existing behaviour. Helper functions podman_bin() and skopeo_bin() are added to bootc-internal-utils and used at every call site across crates/lib and crates/ostree-ext. This unblocks downstream projects that ship a single alternative binary (e.g. dtool) in place of both tools by pointing the env var at that binary rather than hard-linking it into /usr/bin. Assisted-by: OpenCode (claude-sonnet-4-6) Signed-off-by: Eric Curtin <eric.curtin@docker.com>
1 parent 07735ee commit e2541bd

9 files changed

Lines changed: 34 additions & 11 deletions

File tree

crates/lib/src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1853,7 +1853,7 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
18531853
);
18541854
}
18551855
// And ensure we're finding the image in the host storage
1856-
let mut cmd = Command::new("skopeo");
1856+
let mut cmd = Command::new(bootc_utils::skopeo_bin());
18571857
set_additional_image_store(&mut cmd, "/run/host-container-storage");
18581858
proxycfg.skopeo_cmd = Some(cmd);
18591859
iid

crates/lib/src/deploy.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::process::Command;
99

1010
use anyhow::{Context, Result, anyhow};
1111
use bootc_kernel_cmdline::utf8::CmdlineOwned;
12+
use bootc_utils::skopeo_bin;
1213
use cap_std::fs::{Dir, MetadataExt};
1314
use cap_std_ext::cap_std;
1415
use cap_std_ext::dirext::CapStdExtDirExt;
@@ -557,7 +558,7 @@ pub(crate) async fn prepare_for_pull_unified(
557558

558559
// Configure the importer to use bootc storage as an additional image store
559560
let mut config = new_proxy_config();
560-
let mut cmd = Command::new("skopeo");
561+
let mut cmd = Command::new(skopeo_bin());
561562
// Use the physical path to bootc storage from the Storage struct
562563
let storage_path = format!(
563564
"{}/{}",

crates/lib/src/image.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub(crate) const IMAGE_DEFAULT: &str = "localhost/bootc";
3030
/// get structured responses.
3131
async fn image_exists_in_host_storage(image: &str) -> Result<bool> {
3232
use tokio::process::Command as AsyncCommand;
33-
let mut cmd = AsyncCommand::new("podman");
33+
let mut cmd = AsyncCommand::new(bootc_utils::podman_bin());
3434
cmd.args(["image", "exists", image]);
3535
Ok(cmd.status().await?.success())
3636
}

crates/lib/src/podman.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub(crate) struct ImageListEntry {
2424
/// Given an image ID, return its manifest digest
2525
pub(crate) fn imageid_to_digest(imgid: &str) -> Result<String> {
2626
use bootc_utils::CommandRunExt;
27-
let o: Vec<Inspect> = crate::install::run_in_host_mountns("podman")?
27+
let o: Vec<Inspect> = crate::install::run_in_host_mountns(bootc_utils::podman_bin())?
2828
.args(["inspect", imgid])
2929
.run_and_parse_json()?;
3030
let i = o

crates/lib/src/podman_client.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ impl PodmanClient {
100100
std::fs::create_dir_all("/run/bootc/").ok();
101101
let _ = std::fs::remove_file(&socket_path);
102102

103-
let mut cmd = std::process::Command::new("podman");
103+
let mut cmd = std::process::Command::new(bootc_utils::podman_bin());
104104
let mut fds = CmdFds::new();
105105
crate::podstorage::bind_storage_roots(&mut cmd, &mut fds, storage_root, run_root)?;
106106
crate::podstorage::setup_auth(&mut cmd, &mut fds, sysroot)?;
@@ -252,7 +252,7 @@ impl PodmanClient {
252252
tracing::debug!(
253253
"Image uses non-docker transport, falling back to podman pull subprocess: {image}"
254254
);
255-
let mut cmd = Command::new("podman");
255+
let mut cmd = Command::new(bootc_utils::podman_bin());
256256
let mut fds = CmdFds::new();
257257
crate::podstorage::bind_storage_roots(
258258
&mut cmd,

crates/lib/src/podstorage.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ pub(crate) fn setup_auth(cmd: &mut Command, fds: &mut CmdFds, sysroot: &Dir) ->
167167
// - storage overridden to point to to storage_root
168168
// - Authentication (auth.json) using the bootc/ostree owned auth
169169
fn new_podman_cmd_in(sysroot: &Dir, storage_root: &Dir, run_root: &Dir) -> Result<Command> {
170-
let mut cmd = Command::new("podman");
170+
let mut cmd = Command::new(bootc_utils::podman_bin());
171171
let mut fds = CmdFds::new();
172172
bind_storage_roots(&mut cmd, &mut fds, storage_root, run_root)?;
173173
let run_root = format!("/proc/self/fd/{STORAGE_RUN_FD}");
@@ -201,7 +201,7 @@ pub fn set_additional_image_store<'c>(
201201
///
202202
/// Call this function any time we're going to write to containers-storage.
203203
pub(crate) fn ensure_floating_c_storage_initialized() {
204-
if let Err(e) = Command::new("podman")
204+
if let Err(e) = Command::new(bootc_utils::podman_bin())
205205
.args(["system", "info"])
206206
.stdout(Stdio::null())
207207
.run_capture_stderr()
@@ -447,7 +447,7 @@ impl CStorage {
447447
/// to this storage.
448448
#[context("Pulling from host storage: {image}")]
449449
pub(crate) async fn pull_from_host_storage(&self, image: &str) -> Result<()> {
450-
let mut cmd = Command::new("podman");
450+
let mut cmd = Command::new(bootc_utils::podman_bin());
451451
cmd.stdin(Stdio::null());
452452
cmd.stdout(Stdio::null());
453453
// An ephemeral place for the transient state;

crates/ostree-ext/src/container/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ pub fn merge_default_container_proxy_opts_with_isolation(
588588
if let Some(authfile) = config.authfile.take() {
589589
config.auth_data = Some(std::fs::File::open(authfile)?);
590590
}
591-
let cmd = crate::isolation::unprivileged_subprocess("skopeo", user);
591+
let cmd = crate::isolation::unprivileged_subprocess(bootc_utils::skopeo_bin(), user);
592592
config.skopeo_cmd = Some(cmd);
593593
}
594594
Ok(())

crates/ostree-ext/src/container/skopeo.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub(crate) fn container_policy_is_default_insecure() -> Result<bool> {
5151

5252
/// Create a Command builder for skopeo.
5353
pub(crate) fn new_cmd() -> std::process::Command {
54-
let mut cmd = std::process::Command::new("skopeo");
54+
let mut cmd = std::process::Command::new(bootc_utils::skopeo_bin());
5555
cmd.stdin(Stdio::null());
5656
cmd
5757
}

crates/utils/src/lib.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,28 @@ pub use tracing_util::*;
2222
/// The name of our binary
2323
pub const NAME: &str = "bootc";
2424

25+
/// Return the podman binary path, honouring the `BOOTC_EXP_EXTERNAL_CONTAINER_TOOL`
26+
/// environment variable so callers can substitute an alternative tool (e.g. `dtool`)
27+
/// without hard-linking it as `/usr/bin/podman`. The _EXP prefix indicates this
28+
/// interface is experimental and subject to change.
29+
pub fn podman_bin() -> &'static str {
30+
static BIN: std::sync::OnceLock<String> = std::sync::OnceLock::new();
31+
BIN.get_or_init(|| {
32+
std::env::var("BOOTC_EXP_EXTERNAL_CONTAINER_TOOL").unwrap_or_else(|_| "podman".to_string())
33+
})
34+
}
35+
36+
/// Return the skopeo binary path, honouring the `BOOTC_EXP_EXTERNAL_CONTAINER_TOOL`
37+
/// environment variable so callers can substitute an alternative tool (e.g. `dtool`)
38+
/// without hard-linking it as `/usr/bin/skopeo`. The _EXP prefix indicates this
39+
/// interface is experimental and subject to change.
40+
pub fn skopeo_bin() -> &'static str {
41+
static BIN: std::sync::OnceLock<String> = std::sync::OnceLock::new();
42+
BIN.get_or_init(|| {
43+
std::env::var("BOOTC_EXP_EXTERNAL_CONTAINER_TOOL").unwrap_or_else(|_| "skopeo".to_string())
44+
})
45+
}
46+
2547
/// Intended for use in `main`, calls an inner function and
2648
/// handles errors by printing them.
2749
pub fn run_main<F>(f: F)

0 commit comments

Comments
 (0)