Propogate use_sudo pattern to all traits that podman can be used in

This commit is contained in:
Gerald Pinder 2025-03-20 23:29:39 -04:00
parent 430d18de8a
commit 883090ee85
14 changed files with 346 additions and 116 deletions

2
Cargo.lock generated
View file

@ -511,6 +511,7 @@ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"blake2", "blake2",
"bon", "bon",
"cached",
"chrono", "chrono",
"clap", "clap",
"comlexr", "comlexr",
@ -521,6 +522,7 @@ dependencies = [
"lenient_semver", "lenient_semver",
"log", "log",
"miette", "miette",
"nix",
"process_control", "process_control",
"rstest", "rstest",
"semver", "semver",

View file

@ -149,7 +149,7 @@ test-docker-build: install-debug-all-features
test-rechunk-build: install-debug-all-features test-rechunk-build: install-debug-all-features
cd integration-tests/test-repo \ cd integration-tests/test-repo \
&& sudo -E {{ cargo_bin }}/bluebuild build \ && bluebuild build \
{{ should_push }} \ {{ should_push }} \
-vv \ -vv \
--rechunk \ --rechunk \
@ -157,7 +157,7 @@ test-rechunk-build: install-debug-all-features
test-fresh-rechunk-build: install-debug-all-features test-fresh-rechunk-build: install-debug-all-features
cd integration-tests/test-repo \ cd integration-tests/test-repo \
&& sudo -E {{ cargo_bin }}/bluebuild build \ && bluebuild build \
{{ should_push }} \ {{ should_push }} \
-vv \ -vv \
--rechunk \ --rechunk \

View file

@ -32,7 +32,7 @@ indicatif.workspace = true
indexmap.workspace = true indexmap.workspace = true
log.workspace = true log.workspace = true
miette.workspace = true miette.workspace = true
nix = { workspace = true, features = ["signal", "user"] } nix = { workspace = true, features = ["signal"] }
oci-distribution.workspace = true oci-distribution.workspace = true
reqwest.workspace = true reqwest.workspace = true
semver.workspace = true semver.workspace = true

View file

@ -24,8 +24,9 @@ use miette::{miette, IntoDiagnostic, Result};
use oci_distribution::Reference; use oci_distribution::Reference;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use opts::{ use opts::{
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateImageNameOpts, GenerateKeyPairOpts, BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, ContainerOpts, CreateContainerOpts,
GenerateTagsOpts, GetMetadataOpts, PushOpts, RunOpts, SignOpts, TagOpts, VerifyOpts, GenerateImageNameOpts, GenerateKeyPairOpts, GenerateTagsOpts, GetMetadataOpts, PushOpts,
RemoveContainerOpts, RemoveImageOpts, RunOpts, SignOpts, TagOpts, VerifyOpts, VolumeOpts,
}; };
use types::{ use types::{
BuildDriverType, CiDriverType, DetermineDriver, ImageMetadata, InspectDriverType, Platform, BuildDriverType, CiDriverType, DetermineDriver, ImageMetadata, InspectDriverType, Platform,
@ -272,7 +273,7 @@ fn get_version_run_image(oci_ref: &Reference) -> Result<u64> {
progress.enable_steady_tick(Duration::from_millis(100)); progress.enable_steady_tick(Duration::from_millis(100));
let should_remove = if matches!(Driver::get_run_driver(), RunDriverType::Docker) { let should_remove = if matches!(Driver::get_run_driver(), RunDriverType::Docker) {
!Driver::list_images()?.contains(oci_ref) !Driver::list_images(false)?.contains(oci_ref)
} else { } else {
false false
}; };
@ -291,7 +292,7 @@ fn get_version_run_image(oci_ref: &Reference) -> Result<u64> {
)?; )?;
if should_remove { if should_remove {
Driver::remove_image(oci_ref)?; Driver::remove_image(&RemoveImageOpts::builder().image(oci_ref).build())?;
} }
progress.finish_and_clear(); progress.finish_and_clear();
@ -407,20 +408,20 @@ impl RunDriver for Driver {
impl_run_driver!(run_output(opts)) impl_run_driver!(run_output(opts))
} }
fn create_container(image: &Reference) -> Result<types::ContainerId> { fn create_container(opts: &CreateContainerOpts) -> Result<types::ContainerId> {
impl_run_driver!(create_container(image)) impl_run_driver!(create_container(opts))
} }
fn remove_container(container_id: &types::ContainerId) -> Result<()> { fn remove_container(opts: &RemoveContainerOpts) -> Result<()> {
impl_run_driver!(remove_container(container_id)) impl_run_driver!(remove_container(opts))
} }
fn remove_image(image: &Reference) -> Result<()> { fn remove_image(opts: &RemoveImageOpts) -> Result<()> {
impl_run_driver!(remove_image(image)) impl_run_driver!(remove_image(opts))
} }
fn list_images() -> Result<Vec<Reference>> { fn list_images(privileged: bool) -> Result<Vec<Reference>> {
impl_run_driver!(list_images()) impl_run_driver!(list_images(privileged))
} }
} }
@ -473,16 +474,16 @@ impl CiDriver for Driver {
#[cfg(feature = "rechunk")] #[cfg(feature = "rechunk")]
impl ContainerMountDriver for Driver { impl ContainerMountDriver for Driver {
fn mount_container(container_id: &types::ContainerId) -> Result<types::MountId> { fn mount_container(opts: &ContainerOpts) -> Result<types::MountId> {
PodmanDriver::mount_container(container_id) PodmanDriver::mount_container(opts)
} }
fn unmount_container(container_id: &types::ContainerId) -> Result<()> { fn unmount_container(opts: &ContainerOpts) -> Result<()> {
PodmanDriver::unmount_container(container_id) PodmanDriver::unmount_container(opts)
} }
fn remove_volume(volume_id: &str) -> Result<()> { fn remove_volume(opts: &VolumeOpts) -> Result<()> {
PodmanDriver::remove_volume(volume_id) PodmanDriver::remove_volume(opts)
} }
} }
@ -501,32 +502,4 @@ impl RechunkDriver for Driver {
fn rechunk(opts: &opts::RechunkOpts) -> Result<Vec<String>> { fn rechunk(opts: &opts::RechunkOpts) -> Result<Vec<String>> {
PodmanDriver::rechunk(opts) PodmanDriver::rechunk(opts)
} }
fn prune_image(
_mount: &types::MountId,
_container: &types::ContainerId,
_raw_image: &Reference,
_opts: &opts::RechunkOpts<'_>,
) -> Result<(), miette::Error> {
unimplemented!("Use the `rechunk` function instead");
}
fn create_ostree_commit(
_mount: &types::MountId,
_ostree_cache_id: &str,
_container: &types::ContainerId,
_raw_image: &Reference,
_opts: &opts::RechunkOpts<'_>,
) -> Result<()> {
unimplemented!("Use the `rechunk` function instead");
}
fn rechunk_image(
_ostree_cache_id: &str,
_temp_dir_str: &str,
_current_dir: &str,
_opts: &opts::RechunkOpts<'_>,
) -> Result<()> {
unimplemented!("Use the `rechunk` function instead");
}
} }

View file

@ -36,6 +36,8 @@ use crate::{
signal_handler::{add_cid, remove_cid, ContainerRuntime, ContainerSignalId}, signal_handler::{add_cid, remove_cid, ContainerRuntime, ContainerSignalId},
}; };
use super::opts::{CreateContainerOpts, RemoveContainerOpts, RemoveImageOpts};
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct DockerVerisonJsonClient { struct DockerVerisonJsonClient {
#[serde(alias = "Version")] #[serde(alias = "Version")]
@ -507,11 +509,11 @@ impl RunDriver for DockerDriver {
Ok(output) Ok(output)
} }
fn create_container(image: &oci_distribution::Reference) -> Result<super::types::ContainerId> { fn create_container(opts: &CreateContainerOpts) -> Result<super::types::ContainerId> {
trace!("DockerDriver::create_container({image})"); trace!("DockerDriver::create_container({opts:?})");
let output = { let output = {
let c = cmd!("docker", "create", image.to_string(), "bash",); let c = cmd!("docker", "create", opts.image.to_string(), "bash",);
trace!("{c:?}"); trace!("{c:?}");
c c
} }
@ -519,7 +521,7 @@ impl RunDriver for DockerDriver {
.into_diagnostic()?; .into_diagnostic()?;
if !output.status.success() { if !output.status.success() {
bail!("Failed to create container from image {image}"); bail!("Failed to create container from image {}", opts.image);
} }
Ok(ContainerId( Ok(ContainerId(
@ -527,11 +529,11 @@ impl RunDriver for DockerDriver {
)) ))
} }
fn remove_container(container_id: &super::types::ContainerId) -> Result<()> { fn remove_container(opts: &RemoveContainerOpts) -> Result<()> {
trace!("DockerDriver::remove_container({container_id})"); trace!("DockerDriver::remove_container({opts:?})");
let output = { let output = {
let c = cmd!("docker", "rm", container_id); let c = cmd!("docker", "rm", opts.container_id);
trace!("{c:?}"); trace!("{c:?}");
c c
} }
@ -539,17 +541,17 @@ impl RunDriver for DockerDriver {
.into_diagnostic()?; .into_diagnostic()?;
if !output.status.success() { if !output.status.success() {
bail!("Failed to remove container {container_id}"); bail!("Failed to remove container {}", opts.container_id);
} }
Ok(()) Ok(())
} }
fn remove_image(image: &oci_distribution::Reference) -> Result<()> { fn remove_image(opts: &RemoveImageOpts) -> Result<()> {
trace!("DockerDriver::remove_image({image})"); trace!("DockerDriver::remove_image({opts:?})");
let output = { let output = {
let c = cmd!("docker", "rmi", image.to_string()); let c = cmd!("docker", "rmi", opts.image.to_string());
trace!("{c:?}"); trace!("{c:?}");
c c
} }
@ -557,13 +559,13 @@ impl RunDriver for DockerDriver {
.into_diagnostic()?; .into_diagnostic()?;
if !output.status.success() { if !output.status.success() {
bail!("Failed to remove the image {image}"); bail!("Failed to remove the image {}", opts.image);
} }
Ok(()) Ok(())
} }
fn list_images() -> Result<Vec<Reference>> { fn list_images(_privileged: bool) -> Result<Vec<Reference>> {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
struct Image { struct Image {

View file

@ -24,18 +24,27 @@ pub struct BuildOpts<'scope> {
#[builder(default)] #[builder(default)]
pub host_network: bool, pub host_network: bool,
#[builder(default)]
pub privileged: bool,
} }
#[derive(Debug, Clone, Builder)] #[derive(Debug, Clone, Builder)]
pub struct TagOpts<'scope> { pub struct TagOpts<'scope> {
pub src_image: &'scope Reference, pub src_image: &'scope Reference,
pub dest_image: &'scope Reference, pub dest_image: &'scope Reference,
#[builder(default)]
pub privileged: bool,
} }
#[derive(Debug, Clone, Builder)] #[derive(Debug, Clone, Builder)]
pub struct PushOpts<'scope> { pub struct PushOpts<'scope> {
pub image: &'scope Reference, pub image: &'scope Reference,
pub compression_type: Option<CompressionType>, pub compression_type: Option<CompressionType>,
#[builder(default)]
pub privileged: bool,
} }
#[derive(Debug, Clone, Builder)] #[derive(Debug, Clone, Builder)]
@ -95,4 +104,8 @@ pub struct BuildTagPushOpts<'scope> {
/// The platform to build the image on. /// The platform to build the image on.
#[builder(default)] #[builder(default)]
pub platform: Platform, pub platform: Platform,
/// Runs the build with elevated privileges
#[builder(default)]
pub privileged: bool,
} }

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, path::Path};
use bon::Builder; use bon::Builder;
use crate::drivers::types::Platform; use crate::drivers::types::{ContainerId, Platform};
use super::CompressionType; use super::CompressionType;
@ -49,3 +49,20 @@ pub struct RechunkOpts<'scope> {
#[builder(default)] #[builder(default)]
pub clear_plan: bool, pub clear_plan: bool,
} }
#[derive(Debug, Clone, Builder)]
pub struct ContainerOpts<'scope> {
pub container_id: &'scope ContainerId,
#[builder(default)]
pub privileged: bool,
}
#[derive(Debug, Clone, Builder)]
pub struct VolumeOpts<'scope> {
#[builder(into)]
pub volume_id: Cow<'scope, str>,
#[builder(default)]
pub privileged: bool,
}

View file

@ -1,6 +1,9 @@
use std::borrow::Cow; use std::borrow::Cow;
use bon::Builder; use bon::Builder;
use oci_distribution::Reference;
use crate::drivers::types::ContainerId;
#[derive(Debug, Clone, Builder)] #[derive(Debug, Clone, Builder)]
pub struct RunOpts<'scope> { pub struct RunOpts<'scope> {
@ -74,3 +77,27 @@ macro_rules! run_envs {
} }
}; };
} }
#[derive(Debug, Clone, Builder)]
pub struct CreateContainerOpts<'scope> {
pub image: &'scope Reference,
#[builder(default)]
pub privileged: bool,
}
#[derive(Debug, Clone, Builder)]
pub struct RemoveContainerOpts<'scope> {
pub container_id: &'scope ContainerId,
#[builder(default)]
pub privileged: bool,
}
#[derive(Debug, Clone, Builder)]
pub struct RemoveImageOpts<'scope> {
pub image: &'scope Reference,
#[builder(default)]
pub privileged: bool,
}

View file

@ -5,7 +5,7 @@ use std::{
time::Duration, time::Duration,
}; };
use blue_build_utils::{credentials::Credentials, semver::Version}; use blue_build_utils::{credentials::Credentials, running_as_root, semver::Version};
use cached::proc_macro::cached; use cached::proc_macro::cached;
use colored::Colorize; use colored::Colorize;
use comlexr::{cmd, pipe}; use comlexr::{cmd, pipe};
@ -26,7 +26,10 @@ use crate::{
signal_handler::{add_cid, remove_cid, ContainerRuntime, ContainerSignalId}, signal_handler::{add_cid, remove_cid, ContainerRuntime, ContainerSignalId},
}; };
use super::types::ContainerId; use super::{
opts::{ContainerOpts, CreateContainerOpts, RemoveContainerOpts, RemoveImageOpts, VolumeOpts},
types::ContainerId,
};
#[cfg(feature = "rechunk")] #[cfg(feature = "rechunk")]
use super::{types::MountId, ContainerMountDriver, RechunkDriver}; use super::{types::MountId, ContainerMountDriver, RechunkDriver};
@ -44,9 +47,6 @@ impl TryFrom<Vec<PodmanImageMetadata>> for ImageMetadata {
if value.is_empty() { if value.is_empty() {
bail!("Podman inspection must have at least one metadata entry:\n{value:?}"); bail!("Podman inspection must have at least one metadata entry:\n{value:?}");
} }
if value.is_empty() {
bail!("Need at least one metadata entry:\n{value:?}");
}
let mut value = value.swap_remove(0); let mut value = value.swap_remove(0);
if value.repo_digests.is_empty() { if value.repo_digests.is_empty() {
@ -134,8 +134,17 @@ impl BuildDriver for PodmanDriver {
fn build(opts: &BuildOpts) -> Result<()> { fn build(opts: &BuildOpts) -> Result<()> {
trace!("PodmanDriver::build({opts:#?})"); trace!("PodmanDriver::build({opts:#?})");
let use_sudo = opts.privileged && !running_as_root();
let command = cmd!( let command = cmd!(
"podman", if use_sudo {
"sudo"
} else {
"podman"
},
if use_sudo => [
"-A",
"podman",
],
"build", "build",
if !matches!(opts.platform, Platform::Native) => [ if !matches!(opts.platform, Platform::Native) => [
"--platform", "--platform",
@ -169,7 +178,21 @@ impl BuildDriver for PodmanDriver {
let dest_image_str = opts.dest_image.to_string(); let dest_image_str = opts.dest_image.to_string();
let mut command = cmd!("podman", "tag", opts.src_image.to_string(), &dest_image_str); let use_sudo = opts.privileged && !running_as_root();
let mut command = cmd!(
if use_sudo {
"sudo"
} else {
"podman"
},
if use_sudo => [
"-A",
"podman",
],
"tag",
opts.src_image.to_string(),
&dest_image_str
);
trace!("{command:?}"); trace!("{command:?}");
let status = command.status().into_diagnostic()?; let status = command.status().into_diagnostic()?;
@ -187,8 +210,17 @@ impl BuildDriver for PodmanDriver {
let image_str = opts.image.to_string(); let image_str = opts.image.to_string();
let use_sudo = opts.privileged && !running_as_root();
let command = cmd!( let command = cmd!(
"podman", if use_sudo {
"sudo"
} else {
"podman"
},
if use_sudo => [
"-A",
"podman",
],
"push", "push",
format!( format!(
"--compression-format={}", "--compression-format={}",
@ -345,9 +377,22 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result<ImageMetadata> {
#[cfg(feature = "rechunk")] #[cfg(feature = "rechunk")]
impl ContainerMountDriver for PodmanDriver { impl ContainerMountDriver for PodmanDriver {
fn mount_container(container_id: &super::types::ContainerId) -> Result<MountId> { fn mount_container(opts: &ContainerOpts) -> Result<MountId> {
let use_sudo = opts.privileged && !running_as_root();
let output = { let output = {
let c = cmd!("podman", "mount", container_id); let c = cmd!(
if use_sudo {
"sudo"
} else {
"podman"
},
if use_sudo => [
"-A",
"podman",
],
"mount",
opts.container_id,
);
trace!("{c:?}"); trace!("{c:?}");
c c
} }
@ -355,7 +400,7 @@ impl ContainerMountDriver for PodmanDriver {
.into_diagnostic()?; .into_diagnostic()?;
if !output.status.success() { if !output.status.success() {
bail!("Failed to mount container {container_id}"); bail!("Failed to mount container {}", opts.container_id);
} }
Ok(MountId( Ok(MountId(
@ -363,9 +408,22 @@ impl ContainerMountDriver for PodmanDriver {
)) ))
} }
fn unmount_container(container_id: &super::types::ContainerId) -> Result<()> { fn unmount_container(opts: &ContainerOpts) -> Result<()> {
let use_sudo = opts.privileged && !running_as_root();
let output = { let output = {
let c = cmd!("podman", "unmount", container_id); let c = cmd!(
if use_sudo {
"sudo"
} else {
"podman"
},
if use_sudo => [
"-A",
"podman",
],
"unmount",
opts.container_id
);
trace!("{c:?}"); trace!("{c:?}");
c c
} }
@ -373,15 +431,29 @@ impl ContainerMountDriver for PodmanDriver {
.into_diagnostic()?; .into_diagnostic()?;
if !output.status.success() { if !output.status.success() {
bail!("Failed to unmount container {container_id}"); bail!("Failed to unmount container {}", opts.container_id);
} }
Ok(()) Ok(())
} }
fn remove_volume(volume_id: &str) -> Result<()> { fn remove_volume(opts: &VolumeOpts) -> Result<()> {
let use_sudo = opts.privileged && !running_as_root();
let output = { let output = {
let c = cmd!("podman", "volume", "rm", volume_id); let c = cmd!(
if use_sudo {
"sudo"
} else {
"podman"
},
if use_sudo => [
"-A",
"podman",
],
"volume",
"rm",
&*opts.volume_id
);
trace!("{c:?}"); trace!("{c:?}");
c c
} }
@ -389,7 +461,7 @@ impl ContainerMountDriver for PodmanDriver {
.into_diagnostic()?; .into_diagnostic()?;
if !output.status.success() { if !output.status.success() {
bail!("Failed to remove volume {volume_id}"); bail!("Failed to remove volume {}", &opts.volume_id);
} }
Ok(()) Ok(())
@ -436,9 +508,25 @@ impl RunDriver for PodmanDriver {
Ok(output) Ok(output)
} }
fn create_container(image: &Reference) -> Result<ContainerId> { fn create_container(opts: &CreateContainerOpts) -> Result<ContainerId> {
trace!("PodmanDriver::create_container({opts:?})");
let use_sudo = opts.privileged && !running_as_root();
let output = { let output = {
let c = cmd!("podman", "create", image.to_string(), "bash"); let c = cmd!(
if use_sudo {
"sudo"
} else {
"podman"
},
if use_sudo => [
"-A",
"podman",
],
"create",
opts.image.to_string(),
"bash"
);
trace!("{c:?}"); trace!("{c:?}");
c c
} }
@ -446,7 +534,7 @@ impl RunDriver for PodmanDriver {
.into_diagnostic()?; .into_diagnostic()?;
if !output.status.success() { if !output.status.success() {
bail!("Failed to create a container from image {image}"); bail!("Failed to create a container from image {}", opts.image);
} }
Ok(ContainerId( Ok(ContainerId(
@ -454,9 +542,24 @@ impl RunDriver for PodmanDriver {
)) ))
} }
fn remove_container(container_id: &super::types::ContainerId) -> Result<()> { fn remove_container(opts: &RemoveContainerOpts) -> Result<()> {
trace!("PodmanDriver::remove_container({opts:?})");
let use_sudo = opts.privileged && !running_as_root();
let output = { let output = {
let c = cmd!("podman", "rm", container_id); let c = cmd!(
if use_sudo {
"sudo"
} else {
"podman"
},
if use_sudo => [
"-A",
"podman",
],
"rm",
opts.container_id,
);
trace!("{c:?}"); trace!("{c:?}");
c c
} }
@ -464,15 +567,30 @@ impl RunDriver for PodmanDriver {
.into_diagnostic()?; .into_diagnostic()?;
if !output.status.success() { if !output.status.success() {
bail!("Failed to remove container {container_id}"); bail!("Failed to remove container {}", opts.container_id);
} }
Ok(()) Ok(())
} }
fn remove_image(image: &Reference) -> Result<()> { fn remove_image(opts: &RemoveImageOpts) -> Result<()> {
trace!("PodmanDriver::remove_image({opts:?})");
let use_sudo = opts.privileged && !running_as_root();
let output = { let output = {
let c = cmd!("podman", "rmi", image.to_string()); let c = cmd!(
if use_sudo {
"sudo"
} else {
"podman"
},
if use_sudo => [
"-A",
"podman",
],
"rmi",
opts.image.to_string()
);
trace!("{c:?}"); trace!("{c:?}");
c c
} }
@ -480,21 +598,37 @@ impl RunDriver for PodmanDriver {
.into_diagnostic()?; .into_diagnostic()?;
if !output.status.success() { if !output.status.success() {
bail!("Failed to remove the image {image}"); bail!("Failed to remove the image {}", opts.image);
} }
Ok(()) Ok(())
} }
fn list_images() -> Result<Vec<Reference>> { fn list_images(privileged: bool) -> Result<Vec<Reference>> {
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
struct Image { struct Image {
names: Option<Vec<String>>, names: Option<Vec<String>>,
} }
trace!("PodmanDriver::list_images({privileged})");
let use_sudo = privileged && !running_as_root();
let output = { let output = {
let c = cmd!("podman", "images", "--format", "json"); let c = cmd!(
if use_sudo {
"sudo"
} else {
"podman"
},
if use_sudo => [
"-A",
"podman",
],
"images",
"--format",
"json"
);
trace!("{c:?}"); trace!("{c:?}");
c c
} }
@ -520,7 +654,7 @@ impl RunDriver for PodmanDriver {
} }
fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command { fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command {
let use_sudo = opts.privileged && !nix::unistd::Uid::effective().is_root(); let use_sudo = opts.privileged && !running_as_root();
let command = cmd!( let command = cmd!(
if use_sudo { if use_sudo {
"sudo" "sudo"

View file

@ -22,9 +22,10 @@ use super::{
gitlab_driver::GitlabDriver, gitlab_driver::GitlabDriver,
local_driver::LocalDriver, local_driver::LocalDriver,
opts::{ opts::{
BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateImageNameOpts, GenerateKeyPairOpts, BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, ContainerOpts, CreateContainerOpts,
GenerateTagsOpts, GetMetadataOpts, PushOpts, RunOpts, SignOpts, SignVerifyOpts, TagOpts, GenerateImageNameOpts, GenerateKeyPairOpts, GenerateTagsOpts, GetMetadataOpts, PushOpts,
VerifyOpts, VerifyType, RemoveContainerOpts, RemoveImageOpts, RunOpts, SignOpts, SignVerifyOpts, TagOpts,
VerifyOpts, VerifyType, VolumeOpts,
}, },
podman_driver::PodmanDriver, podman_driver::PodmanDriver,
skopeo_driver::SkopeoDriver, skopeo_driver::SkopeoDriver,
@ -218,25 +219,25 @@ pub trait RunDriver: PrivateDriver {
/// ///
/// # Errors /// # Errors
/// Will error if the container create command fails. /// Will error if the container create command fails.
fn create_container(image: &Reference) -> Result<ContainerId>; fn create_container(opts: &CreateContainerOpts) -> Result<ContainerId>;
/// Removes a container /// Removes a container
/// ///
/// # Errors /// # Errors
/// Will error if the container remove command fails. /// Will error if the container remove command fails.
fn remove_container(container_id: &ContainerId) -> Result<()>; fn remove_container(opts: &RemoveContainerOpts) -> Result<()>;
/// Removes an image /// Removes an image
/// ///
/// # Errors /// # Errors
/// Will error if the image remove command fails. /// Will error if the image remove command fails.
fn remove_image(image: &Reference) -> Result<()>; fn remove_image(opts: &RemoveImageOpts) -> Result<()>;
/// List all images in the local image registry. /// List all images in the local image registry.
/// ///
/// # Errors /// # Errors
/// Will error if the image list command fails. /// Will error if the image list command fails.
fn list_images() -> Result<Vec<Reference>>; fn list_images(privileged: bool) -> Result<Vec<Reference>>;
} }
#[allow(private_bounds)] #[allow(private_bounds)]
@ -246,19 +247,19 @@ pub(super) trait ContainerMountDriver: PrivateDriver {
/// ///
/// # Errors /// # Errors
/// Will error if the container mount command fails. /// Will error if the container mount command fails.
fn mount_container(container_id: &ContainerId) -> Result<MountId>; fn mount_container(opts: &ContainerOpts) -> Result<MountId>;
/// Unmount the container /// Unmount the container
/// ///
/// # Errors /// # Errors
/// Will error if the container unmount command fails. /// Will error if the container unmount command fails.
fn unmount_container(container_id: &ContainerId) -> Result<()>; fn unmount_container(opts: &ContainerOpts) -> Result<()>;
/// Remove a volume /// Remove a volume
/// ///
/// # Errors /// # Errors
/// Will error if the volume remove command fails. /// Will error if the volume remove command fails.
fn remove_volume(volume_id: &str) -> Result<()>; fn remove_volume(opts: &VolumeOpts) -> Result<()>;
} }
#[cfg(feature = "rechunk")] #[cfg(feature = "rechunk")]
@ -295,13 +296,24 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
.image(raw_image.to_string()) .image(raw_image.to_string())
.containerfile(&*opts.containerfile) .containerfile(&*opts.containerfile)
.platform(opts.platform) .platform(opts.platform)
.privileged(true)
.squash(true) .squash(true)
.host_network(true) .host_network(true)
.build(), .build(),
)?; )?;
let container = &Self::create_container(raw_image)?; let container = &Self::create_container(
let mount = &Self::mount_container(container)?; &CreateContainerOpts::builder()
.image(raw_image)
.privileged(true)
.build(),
)?;
let mount = &Self::mount_container(
&ContainerOpts::builder()
.container_id(container)
.privileged(true)
.build(),
)?;
Self::prune_image(mount, container, raw_image, opts)?; Self::prune_image(mount, container, raw_image, opts)?;
Self::create_ostree_commit(mount, ostree_cache_id, container, raw_image, opts)?; Self::create_ostree_commit(mount, ostree_cache_id, container, raw_image, opts)?;
@ -366,9 +378,24 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
)?; )?;
if !status.success() { if !status.success() {
Self::unmount_container(container)?; Self::unmount_container(
Self::remove_container(container)?; &ContainerOpts::builder()
Self::remove_image(raw_image)?; .container_id(container)
.privileged(true)
.build(),
)?;
Self::remove_container(
&RemoveContainerOpts::builder()
.container_id(container)
.privileged(true)
.build(),
)?;
Self::remove_image(
&RemoveImageOpts::builder()
.image(raw_image)
.privileged(true)
.build(),
)?;
bail!("Failed to run prune step for {}", &opts.image); bail!("Failed to run prune step for {}", &opts.image);
} }
@ -404,9 +431,24 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
.args(bon::vec!["/sources/rechunk/2_create.sh"]) .args(bon::vec!["/sources/rechunk/2_create.sh"])
.build(), .build(),
)?; )?;
Self::unmount_container(container)?; Self::unmount_container(
Self::remove_container(container)?; &ContainerOpts::builder()
Self::remove_image(raw_image)?; .container_id(container)
.privileged(true)
.build(),
)?;
Self::remove_container(
&RemoveContainerOpts::builder()
.container_id(container)
.privileged(true)
.build(),
)?;
Self::remove_image(
&RemoveImageOpts::builder()
.image(raw_image)
.privileged(true)
.build(),
)?;
if !status.success() { if !status.success() {
bail!("Failed to run Ostree create step for {}", &opts.image); bail!("Failed to run Ostree create step for {}", &opts.image);
@ -460,7 +502,12 @@ pub trait RechunkDriver: RunDriver + BuildDriver + ContainerMountDriver {
.build(), .build(),
)?; )?;
Self::remove_volume(ostree_cache_id)?; Self::remove_volume(
&VolumeOpts::builder()
.volume_id(ostree_cache_id)
.privileged(true)
.build(),
)?;
if !status.success() { if !status.success() {
bail!("Failed to run rechunking for {}", &opts.image); bail!("Failed to run rechunking for {}", &opts.image);

View file

@ -244,6 +244,7 @@ impl ImageMetadata {
} }
} }
#[derive(Debug, Clone)]
pub struct ContainerId(pub(super) String); pub struct ContainerId(pub(super) String);
impl std::fmt::Display for ContainerId { impl std::fmt::Display for ContainerId {

View file

@ -150,11 +150,6 @@ impl BlueBuildCommand for BuildCommand {
fn try_run(&mut self) -> Result<()> { fn try_run(&mut self) -> Result<()> {
trace!("BuildCommand::try_run()"); trace!("BuildCommand::try_run()");
#[cfg(feature = "rechunk")]
if !nix::unistd::Uid::effective().is_root() && self.rechunk {
bail!("You must be root to use the rechunk feature!");
}
Driver::init(self.drivers); Driver::init(self.drivers);
Credentials::init(self.credentials.clone()); Credentials::init(self.credentials.clone());

View file

@ -20,11 +20,13 @@ lenient_semver = "0.4"
process_control = { version = "4", features = ["crossbeam-channel"] } process_control = { version = "4", features = ["crossbeam-channel"] }
which = "7" which = "7"
cached.workspace = true
chrono.workspace = true chrono.workspace = true
clap = { workspace = true, features = ["derive", "env"] } clap = { workspace = true, features = ["derive", "env"] }
comlexr.workspace = true comlexr.workspace = true
log.workspace = true log.workspace = true
miette.workspace = true miette.workspace = true
nix = { workspace = true, features = ["user"] }
semver = { workspace = true, features = ["serde"] } semver = { workspace = true, features = ["serde"] }
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true

View file

@ -9,6 +9,7 @@ pub mod test_utils;
pub mod traits; pub mod traits;
use std::{ use std::{
ops::Not,
os::unix::ffi::OsStrExt, os::unix::ffi::OsStrExt,
path::{Path, PathBuf}, path::{Path, PathBuf},
thread, thread,
@ -20,6 +21,7 @@ use blake2::{
digest::{Update, VariableOutput}, digest::{Update, VariableOutput},
Blake2bVar, Blake2bVar,
}; };
use cached::proc_macro::once;
use chrono::Local; use chrono::Local;
use comlexr::cmd; use comlexr::cmd;
use format_serde_error::SerdeError; use format_serde_error::SerdeError;
@ -129,3 +131,18 @@ pub fn get_env_var(key: &str) -> Result<String> {
.into_diagnostic() .into_diagnostic()
.with_context(|| format!("Failed to get {key}'")) .with_context(|| format!("Failed to get {key}'"))
} }
/// Checks if an environment variable is set and isn't empty.
#[must_use]
pub fn has_env_var(key: &str) -> bool {
get_env_var(key).is_ok_and(|v| v.is_empty().not())
}
/// Checks if the process is running as root.
///
/// This call is cached to reduce syscalls.
#[once]
#[must_use]
pub fn running_as_root() -> bool {
nix::unistd::Uid::effective().is_root()
}