feat(prune)!: Create prune command
This commit is contained in:
parent
879dca348c
commit
1671ea2143
13 changed files with 356 additions and 70 deletions
|
|
@ -54,3 +54,4 @@ workspace = true
|
|||
[features]
|
||||
sigstore = ["dep:tokio", "dep:sigstore"]
|
||||
validate = ["dep:tokio"]
|
||||
prune = []
|
||||
|
|
|
|||
|
|
@ -326,6 +326,11 @@ impl BuildDriver for Driver {
|
|||
impl_build_driver!(login())
|
||||
}
|
||||
|
||||
#[cfg(feature = "prune")]
|
||||
fn prune(opts: &opts::PruneOpts) -> Result<()> {
|
||||
impl_build_driver!(prune(opts))
|
||||
}
|
||||
|
||||
fn build_tag_push(opts: &BuildTagPushOpts) -> Result<Vec<String>> {
|
||||
impl_build_driver!(build_tag_push(opts))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,13 @@ pub struct BuildahDriver;
|
|||
impl DriverVersion for BuildahDriver {
|
||||
// RUN mounts for bind, cache, and tmpfs first supported in 1.24.0
|
||||
// https://buildah.io/releases/#changes-for-v1240
|
||||
#[cfg(not(feature = "prune"))]
|
||||
const VERSION_REQ: &'static str = ">=1.24";
|
||||
|
||||
// The prune command wasn't present until 1.29
|
||||
#[cfg(feature = "prune")]
|
||||
const VERSION_REQ: &'static str = ">=1.29";
|
||||
|
||||
fn version() -> Result<Version> {
|
||||
trace!("BuildahDriver::version()");
|
||||
|
||||
|
|
@ -64,7 +69,7 @@ impl BuildDriver for BuildahDriver {
|
|||
|
||||
trace!("{command:?}");
|
||||
let status = command
|
||||
.status_image_ref_progress(&opts.image, "Building Image")
|
||||
.build_status(&opts.image, "Building Image")
|
||||
.into_diagnostic()?;
|
||||
|
||||
if status.success() {
|
||||
|
|
@ -104,7 +109,7 @@ impl BuildDriver for BuildahDriver {
|
|||
|
||||
trace!("{command:?}");
|
||||
let status = command
|
||||
.status_image_ref_progress(&opts.image, "Pushing Image")
|
||||
.build_status(&opts.image, "Pushing Image")
|
||||
.into_diagnostic()?;
|
||||
|
||||
if status.success() {
|
||||
|
|
@ -159,4 +164,24 @@ impl BuildDriver for BuildahDriver {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "prune")]
|
||||
fn prune(opts: &super::opts::PruneOpts) -> Result<()> {
|
||||
trace!("PodmanDriver::prune({opts:?})");
|
||||
|
||||
let status = cmd!(
|
||||
"buildah",
|
||||
"prune",
|
||||
"--force",
|
||||
if opts.all => "-all",
|
||||
)
|
||||
.message_status("buildah prune", "Pruning Buildah System")
|
||||
.into_diagnostic()?;
|
||||
|
||||
if !status.success() {
|
||||
bail!("Failed to prune buildah");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,6 +228,59 @@ impl BuildDriver for DockerDriver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "prune")]
|
||||
fn prune(opts: &super::opts::PruneOpts) -> Result<()> {
|
||||
trace!("DockerDriver::prune({opts:?})");
|
||||
|
||||
let (system, buildx) = std::thread::scope(
|
||||
|scope| -> std::thread::Result<(Result<ExitStatus>, Result<ExitStatus>)> {
|
||||
let system = scope.spawn(|| {
|
||||
cmd!(
|
||||
"docker",
|
||||
"system",
|
||||
"prune",
|
||||
"--force",
|
||||
if opts.all => "--all",
|
||||
if opts.volumes => "--volumes",
|
||||
)
|
||||
.message_status("docker system prune", "Pruning Docker System")
|
||||
.into_diagnostic()
|
||||
});
|
||||
|
||||
let buildx = scope.spawn(|| {
|
||||
cmd!(
|
||||
"docker",
|
||||
"buildx",
|
||||
"prune",
|
||||
"--force",
|
||||
|command|? {
|
||||
if !env::var(DOCKER_HOST).is_ok_and(|dh| !dh.is_empty()) {
|
||||
Self::setup()?;
|
||||
cmd!(command, "--builder=bluebuild");
|
||||
}
|
||||
},
|
||||
if opts.all => "--all",
|
||||
)
|
||||
.message_status("docker buildx prune", "Pruning Docker Buildx")
|
||||
.into_diagnostic()
|
||||
});
|
||||
|
||||
Ok((system.join()?, buildx.join()?))
|
||||
},
|
||||
)
|
||||
.map_err(|e| miette!("{e:?}"))?;
|
||||
|
||||
if !system?.success() {
|
||||
bail!("Failed to prune docker system");
|
||||
}
|
||||
|
||||
if !buildx?.success() {
|
||||
bail!("Failed to prune docker buildx");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_tag_push(opts: &BuildTagPushOpts) -> Result<Vec<String>> {
|
||||
trace!("DockerDriver::build_tag_push({opts:#?})");
|
||||
|
||||
|
|
@ -305,7 +358,7 @@ impl BuildDriver for DockerDriver {
|
|||
|
||||
trace!("{command:?}");
|
||||
if command
|
||||
.status_image_ref_progress(display_image, "Building Image")
|
||||
.build_status(display_image, "Building Image")
|
||||
.into_diagnostic()?
|
||||
.success()
|
||||
{
|
||||
|
|
@ -383,8 +436,7 @@ impl RunDriver for DockerDriver {
|
|||
|
||||
add_cid(&cid);
|
||||
|
||||
let status = docker_run(opts, &cid_file)
|
||||
.status_image_ref_progress(&*opts.image, "Running container")?;
|
||||
let status = docker_run(opts, &cid_file).build_status(&*opts.image, "Running container")?;
|
||||
|
||||
remove_cid(&cid);
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,13 @@ pub struct PushOpts<'scope> {
|
|||
pub compression_type: Option<CompressionType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
#[cfg(feature = "prune")]
|
||||
pub struct PruneOpts {
|
||||
pub all: bool,
|
||||
pub volumes: bool,
|
||||
}
|
||||
|
||||
/// Options for building, tagging, and pusing images.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ impl BuildDriver for PodmanDriver {
|
|||
|
||||
trace!("{command:?}");
|
||||
let status = command
|
||||
.status_image_ref_progress(&opts.image, "Building Image")
|
||||
.build_status(&opts.image, "Building Image")
|
||||
.into_diagnostic()?;
|
||||
|
||||
if status.success() {
|
||||
|
|
@ -188,7 +188,7 @@ impl BuildDriver for PodmanDriver {
|
|||
|
||||
trace!("{command:?}");
|
||||
let status = command
|
||||
.status_image_ref_progress(&opts.image, "Pushing Image")
|
||||
.build_status(&opts.image, "Pushing Image")
|
||||
.into_diagnostic()?;
|
||||
|
||||
if status.success() {
|
||||
|
|
@ -243,6 +243,28 @@ impl BuildDriver for PodmanDriver {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "prune")]
|
||||
fn prune(opts: &super::opts::PruneOpts) -> Result<()> {
|
||||
trace!("PodmanDriver::prune({opts:?})");
|
||||
|
||||
let status = cmd!(
|
||||
"podman",
|
||||
"system",
|
||||
"prune",
|
||||
"--force",
|
||||
if opts.all => "-all",
|
||||
if opts.volumes => "--volumes",
|
||||
)
|
||||
.message_status("podman system prune", "Pruning Podman System")
|
||||
.into_diagnostic()?;
|
||||
|
||||
if !status.success() {
|
||||
bail!("Failed to prune podman");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl InspectDriver for PodmanDriver {
|
||||
|
|
@ -326,8 +348,7 @@ impl RunDriver for PodmanDriver {
|
|||
let status = if opts.privileged {
|
||||
podman_run(opts, &cid_file).status()?
|
||||
} else {
|
||||
podman_run(opts, &cid_file)
|
||||
.status_image_ref_progress(&*opts.image, "Running container")?
|
||||
podman_run(opts, &cid_file).build_status(&*opts.image, "Running container")?
|
||||
};
|
||||
|
||||
remove_cid(&cid);
|
||||
|
|
|
|||
|
|
@ -106,6 +106,13 @@ pub trait BuildDriver: PrivateDriver {
|
|||
/// Will error if login fails.
|
||||
fn login() -> Result<()>;
|
||||
|
||||
/// Runs prune commands for the driver.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if the driver fails to prune.
|
||||
#[cfg(feature = "prune")]
|
||||
fn prune(opts: &super::opts::PruneOpts) -> Result<()>;
|
||||
|
||||
/// Runs the logic for building, tagging, and pushing an image.
|
||||
///
|
||||
/// # Errors
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
env,
|
||||
fs::OpenOptions,
|
||||
io::{BufRead, BufReader, Result, Write as IoWrite},
|
||||
|
|
@ -31,10 +32,17 @@ use log4rs::{
|
|||
};
|
||||
use nu_ansi_term::Color;
|
||||
use once_cell::sync::Lazy;
|
||||
use private::Private;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::signal_handler::{add_pid, remove_pid};
|
||||
|
||||
mod private {
|
||||
pub trait Private {}
|
||||
}
|
||||
|
||||
impl Private for Command {}
|
||||
|
||||
static MULTI_PROGRESS: Lazy<MultiProgress> = Lazy::new(MultiProgress::new);
|
||||
static LOG_DIR: Lazy<Mutex<PathBuf>> = Lazy::new(|| Mutex::new(PathBuf::new()));
|
||||
|
||||
|
|
@ -174,87 +182,159 @@ impl ColoredLevel for Level {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait CommandLogging {
|
||||
pub trait CommandLogging: Private {
|
||||
/// Prints each line of stdout/stderr with an image ref string
|
||||
/// and a progress spinner. This helps to keep track of every
|
||||
/// build running in parallel.
|
||||
/// and a progress spinner while also logging the build output.
|
||||
/// This helps to keep track of every build running in parallel.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if there was an issue executing the process.
|
||||
fn status_image_ref_progress<T, U>(self, image_ref: T, message: U) -> Result<ExitStatus>
|
||||
fn build_status<T, U>(self, image_ref: T, message: U) -> Result<ExitStatus>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
U: AsRef<str>;
|
||||
|
||||
/// Prints each line of stdout/stderr with a log header
|
||||
/// and a progress spinner. This helps to keep track of every
|
||||
/// command running in parallel.
|
||||
///
|
||||
/// # Errors
|
||||
/// Will error if there was an issue executing the process.
|
||||
fn message_status<S, D>(self, header: S, message: D) -> Result<ExitStatus>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
D: Into<Cow<'static, str>>;
|
||||
}
|
||||
|
||||
impl CommandLogging for Command {
|
||||
fn status_image_ref_progress<T, U>(mut self, image_ref: T, message: U) -> Result<ExitStatus>
|
||||
fn build_status<T, U>(self, image_ref: T, message: U) -> Result<ExitStatus>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
U: AsRef<str>,
|
||||
{
|
||||
let ansi_color = gen_random_ansi_color();
|
||||
let name = color_str(&image_ref, ansi_color);
|
||||
let short_name = color_str(shorten_name(&image_ref), ansi_color);
|
||||
let (reader, writer) = os_pipe::pipe()?;
|
||||
fn inner(mut command: Command, image_ref: &str, message: &str) -> Result<ExitStatus> {
|
||||
let ansi_color = gen_random_ansi_color();
|
||||
let name = color_str(image_ref, ansi_color);
|
||||
let short_name = color_str(shorten_name(image_ref), ansi_color);
|
||||
let (reader, writer) = os_pipe::pipe()?;
|
||||
|
||||
self.stdout(writer.try_clone()?)
|
||||
.stderr(writer)
|
||||
.stdin(Stdio::piped());
|
||||
command
|
||||
.stdout(writer.try_clone()?)
|
||||
.stderr(writer)
|
||||
.stdin(Stdio::piped());
|
||||
|
||||
let progress = Logger::multi_progress()
|
||||
.add(ProgressBar::new_spinner().with_message(format!("{} {name}", message.as_ref())));
|
||||
progress.enable_steady_tick(Duration::from_millis(100));
|
||||
let progress = Logger::multi_progress()
|
||||
.add(ProgressBar::new_spinner().with_message(format!("{message} {name}")));
|
||||
progress.enable_steady_tick(Duration::from_millis(100));
|
||||
|
||||
let mut child = self.spawn()?;
|
||||
let mut child = command.spawn()?;
|
||||
|
||||
let child_pid = child.id();
|
||||
add_pid(child_pid);
|
||||
let child_pid = child.id();
|
||||
add_pid(child_pid);
|
||||
|
||||
// We drop the `Command` to prevent blocking on writer
|
||||
// https://docs.rs/os_pipe/latest/os_pipe/#examples
|
||||
drop(self);
|
||||
// We drop the `Command` to prevent blocking on writer
|
||||
// https://docs.rs/os_pipe/latest/os_pipe/#examples
|
||||
drop(command);
|
||||
|
||||
let reader = BufReader::new(reader);
|
||||
let log_file_path = {
|
||||
let lock = LOG_DIR.lock().expect("Should lock LOG_DIR");
|
||||
lock.join(format!(
|
||||
"{}.log",
|
||||
image_ref.as_ref().replace(['/', ':', '.'], "_")
|
||||
))
|
||||
};
|
||||
let log_file = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(log_file_path.as_path())?;
|
||||
let reader = BufReader::new(reader);
|
||||
let log_file_path = {
|
||||
let lock = LOG_DIR.lock().expect("Should lock LOG_DIR");
|
||||
lock.join(format!("{}.log", image_ref.replace(['/', ':', '.'], "_")))
|
||||
};
|
||||
let log_file = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(log_file_path.as_path())?;
|
||||
|
||||
thread::spawn(move || {
|
||||
let mp = Logger::multi_progress();
|
||||
reader.lines().for_each(|line| {
|
||||
if let Ok(l) = line {
|
||||
let text = format!("{log_prefix} {l}", log_prefix = log_header(&short_name));
|
||||
if mp.is_hidden() {
|
||||
eprintln!("{text}");
|
||||
} else {
|
||||
mp.println(text).unwrap();
|
||||
thread::spawn(move || {
|
||||
let mp = Logger::multi_progress();
|
||||
reader.lines().for_each(|line| {
|
||||
if let Ok(l) = line {
|
||||
let text =
|
||||
format!("{log_prefix} {l}", log_prefix = log_header(&short_name));
|
||||
if mp.is_hidden() {
|
||||
eprintln!("{text}");
|
||||
} else {
|
||||
mp.println(text).unwrap();
|
||||
}
|
||||
if let Err(e) = writeln!(&log_file, "{l}") {
|
||||
warn!(
|
||||
"Failed to write to log for build {}: {e:?}",
|
||||
log_file_path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Err(e) = writeln!(&log_file, "{l}") {
|
||||
warn!(
|
||||
"Failed to write to log for build {}: {e:?}",
|
||||
log_file_path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let status = child.wait()?;
|
||||
remove_pid(child_pid);
|
||||
let status = child.wait()?;
|
||||
remove_pid(child_pid);
|
||||
|
||||
progress.finish();
|
||||
Logger::multi_progress().remove(&progress);
|
||||
progress.finish();
|
||||
Logger::multi_progress().remove(&progress);
|
||||
|
||||
Ok(status)
|
||||
Ok(status)
|
||||
}
|
||||
inner(self, image_ref.as_ref(), message.as_ref())
|
||||
}
|
||||
|
||||
fn message_status<S, D>(self, header: S, message: D) -> Result<ExitStatus>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
D: Into<Cow<'static, str>>,
|
||||
{
|
||||
fn inner(
|
||||
mut command: Command,
|
||||
header: &str,
|
||||
message: Cow<'static, str>,
|
||||
) -> Result<ExitStatus> {
|
||||
let ansi_color = gen_random_ansi_color();
|
||||
let header = color_str(header, ansi_color);
|
||||
let (reader, writer) = os_pipe::pipe()?;
|
||||
|
||||
command
|
||||
.stdout(writer.try_clone()?)
|
||||
.stderr(writer)
|
||||
.stdin(Stdio::piped());
|
||||
|
||||
let progress =
|
||||
Logger::multi_progress().add(ProgressBar::new_spinner().with_message(message));
|
||||
progress.enable_steady_tick(Duration::from_millis(100));
|
||||
|
||||
let mut child = command.spawn()?;
|
||||
|
||||
let child_pid = child.id();
|
||||
add_pid(child_pid);
|
||||
|
||||
// We drop the `Command` to prevent blocking on writer
|
||||
// https://docs.rs/os_pipe/latest/os_pipe/#examples
|
||||
drop(command);
|
||||
|
||||
let reader = BufReader::new(reader);
|
||||
|
||||
thread::spawn(move || {
|
||||
let mp = Logger::multi_progress();
|
||||
reader.lines().for_each(|line| {
|
||||
if let Ok(l) = line {
|
||||
let text = format!("{log_prefix} {l}", log_prefix = log_header(&header));
|
||||
if mp.is_hidden() {
|
||||
eprintln!("{text}");
|
||||
} else {
|
||||
mp.println(text).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let status = child.wait()?;
|
||||
remove_pid(child_pid);
|
||||
|
||||
progress.finish();
|
||||
Logger::multi_progress().remove(&progress);
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
inner(self, header.as_ref(), message.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue