feat: Create RunDriver (#196)

This will be used for running containers for various tasks. There will
be a way to take all output from the process and a way to display output
from a running container like our builds have.
This commit is contained in:
Gerald Pinder 2024-07-05 19:20:38 -04:00 committed by GitHub
parent 1a348f8137
commit 784be9869a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 510 additions and 98 deletions

1
Cargo.lock generated
View file

@ -304,6 +304,7 @@ dependencies = [
"serde_yaml 0.9.34+deprecated",
"signal-hook",
"syntect",
"tempdir",
"typed-builder",
"which",
]

View file

@ -23,6 +23,7 @@ once_cell = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
tempdir = "0.3"
typed-builder = "0.18"
uuid = { version = "1", features = ["v4"] }
@ -69,7 +70,6 @@ rayon = { version = "1.10.0", optional = true }
requestty = { version = "0.5", features = ["macros", "termion"] }
semver = { version = "1", features = ["serde"] }
shadow-rs = "0.26"
tempdir = "0.3"
urlencoding = "2"
users = "0.11"
@ -85,6 +85,7 @@ once_cell.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
tempdir.workspace = true
typed-builder.workspace = true
uuid.workspace = true

View file

@ -123,17 +123,6 @@ pub enum CommandArgs {
#[derive(Default, Clone, Copy, Debug, TypedBuilder, Args)]
pub struct DriverArgs {
/// Runs all instructions inside one layer of the final image.
///
/// WARN: This doesn't work with the
/// docker driver as it has been deprecated.
///
/// NOTE: Squash has a performance benefit for
/// podman and buildah when running inside a container.
#[arg(short, long)]
#[builder(default)]
squash: bool,
/// Select which driver to use to build
/// your image.
#[builder(default)]
@ -146,3 +135,15 @@ pub struct DriverArgs {
#[arg(short = 'I', long)]
inspect_driver: Option<InspectDriverType>,
}
#[cfg(test)]
mod test {
use clap::CommandFactory;
use super::BlueBuildArgs;
#[test]
fn test_cli() {
BlueBuildArgs::command().debug_assert();
}
}

View file

@ -117,6 +117,17 @@ pub struct BuildCommand {
#[builder(default)]
no_sign: bool,
/// Runs all instructions inside one layer of the final image.
///
/// WARN: This doesn't work with the
/// docker driver as it has been deprecated.
///
/// NOTE: Squash has a performance benefit for
/// podman and buildah when running inside a container.
#[arg(short, long)]
#[builder(default)]
squash: bool,
#[clap(flatten)]
#[builder(default)]
drivers: DriverArgs,
@ -134,7 +145,7 @@ impl BlueBuildCommand for BuildCommand {
.build_driver(self.drivers.build_driver)
.inspect_driver(self.drivers.inspect_driver)
.build()
.init()?;
.init();
self.update_gitignore()?;
@ -171,7 +182,7 @@ impl BlueBuildCommand for BuildCommand {
GenerateCommand::builder()
.output(generate_containerfile_path(recipe)?)
.recipe(recipe)
.drivers(DriverArgs::builder().squash(self.drivers.squash).build())
.drivers(self.drivers)
.build()
.try_run()
})?;
@ -195,7 +206,7 @@ impl BlueBuildCommand for BuildCommand {
GenerateCommand::builder()
.output(generate_containerfile_path(&recipe_path)?)
.recipe(&recipe_path)
.drivers(DriverArgs::builder().squash(self.drivers.squash).build())
.drivers(self.drivers)
.build()
.try_run()?;
@ -227,7 +238,7 @@ impl BuildCommand {
archive_dir.to_string_lossy().trim_end_matches('/'),
recipe.name.to_lowercase().replace('/', "_"),
))
.squash(self.drivers.squash)
.squash(self.squash)
.build()
} else {
BuildTagPushOpts::builder()
@ -238,7 +249,7 @@ impl BuildCommand {
.no_retry_push(self.no_retry_push)
.retry_count(self.retry_count)
.compression(self.compression_format)
.squash(self.drivers.squash)
.squash(self.squash)
.build()
};
@ -273,7 +284,7 @@ impl BuildCommand {
archive_dir.to_string_lossy().trim_end_matches('/'),
recipe.name.to_lowercase().replace('/', "_"),
))
.squash(self.drivers.squash)
.squash(self.squash)
.build()
} else {
BuildTagPushOpts::builder()
@ -284,7 +295,7 @@ impl BuildCommand {
.no_retry_push(self.no_retry_push)
.retry_count(self.retry_count)
.compression(self.compression_format)
.squash(self.drivers.squash)
.squash(self.squash)
.build()
};

View file

@ -77,7 +77,7 @@ impl BlueBuildCommand for GenerateCommand {
.build_driver(self.drivers.build_driver)
.inspect_driver(self.drivers.inspect_driver)
.build()
.init()?;
.init();
self.template_file()
}

View file

@ -55,7 +55,7 @@ impl BlueBuildCommand for SwitchCommand {
.build_driver(self.drivers.build_driver)
.inspect_driver(self.drivers.inspect_driver)
.build()
.init()?;
.init();
let status = RpmOstreeStatus::try_new()?;
trace!("{status:?}");

View file

@ -1,6 +1,5 @@
use std::{env, sync::Mutex};
use anyhow::{anyhow, Result};
use blue_build_utils::constants::{
CI_REGISTRY, CI_REGISTRY_PASSWORD, CI_REGISTRY_USER, GITHUB_ACTIONS, GITHUB_ACTOR, GITHUB_TOKEN,
};
@ -107,29 +106,23 @@ static ENV_CREDENTIALS: Lazy<Option<Credentials>> = Lazy::new(|| {
/// any strategy that requires credentials as
/// the environment credentials are lazy allocated.
///
/// # Errors
/// Will error if it can't lock the mutex.
/// # Panics
/// Will panic if it can't lock the mutex.
pub fn set_user_creds(
username: Option<&String>,
password: Option<&String>,
registry: Option<&String>,
) -> Result<()> {
) {
trace!("credentials::set({username:?}, password, {registry:?})");
let mut creds_lock = USER_CREDS
.lock()
.map_err(|e| anyhow!("Failed to set credentials: {e}"))?;
let mut creds_lock = USER_CREDS.lock().expect("Must lock USER_CREDS");
creds_lock.username = username.map(ToOwned::to_owned);
creds_lock.password = password.map(ToOwned::to_owned);
creds_lock.registry = registry.map(ToOwned::to_owned);
drop(creds_lock);
let _ = ENV_CREDENTIALS.as_ref();
Ok(())
}
/// Get the credentials for the current set of actions.
///
/// # Errors
/// Will error if there aren't any credentials available.
pub fn get() -> Option<&'static Credentials> {
trace!("credentials::get()");
ENV_CREDENTIALS.as_ref()

View file

@ -6,6 +6,7 @@
use std::{
collections::{hash_map::Entry, HashMap},
process::{ExitStatus, Output},
sync::{Arc, Mutex},
};
@ -23,10 +24,10 @@ use crate::{credentials, image_metadata::ImageMetadata};
use self::{
buildah_driver::BuildahDriver,
docker_driver::DockerDriver,
opts::{BuildOpts, BuildTagPushOpts, GetMetadataOpts, PushOpts, TagOpts},
opts::{BuildOpts, BuildTagPushOpts, GetMetadataOpts, PushOpts, RunOpts, TagOpts},
podman_driver::PodmanDriver,
skopeo_driver::SkopeoDriver,
types::{BuildDriverType, InspectDriverType},
types::{BuildDriverType, InspectDriverType, RunDriverType},
};
mod buildah_driver;
@ -36,10 +37,11 @@ mod podman_driver;
mod skopeo_driver;
pub mod types;
static INIT: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
static INIT: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
static SELECTED_BUILD_DRIVER: Lazy<Mutex<Option<BuildDriverType>>> = Lazy::new(|| Mutex::new(None));
static SELECTED_INSPECT_DRIVER: Lazy<Mutex<Option<InspectDriverType>>> =
Lazy::new(|| Mutex::new(None));
static SELECTED_RUN_DRIVER: Lazy<Mutex<Option<RunDriverType>>> = Lazy::new(|| Mutex::new(None));
/// Stores the build driver.
///
@ -75,7 +77,7 @@ static BUILD_DRIVER: Lazy<Arc<dyn BuildDriver>> = Lazy::new(|| {
///
/// # Panics
///
/// This will cause a panic if a build strategy could
/// This will cause a panic if a inspect strategy could
/// not be determined.
static INSPECT_DRIVER: Lazy<Arc<dyn InspectDriver>> = Lazy::new(|| {
let driver = SELECTED_INSPECT_DRIVER.lock().unwrap();
@ -91,6 +93,30 @@ static INSPECT_DRIVER: Lazy<Arc<dyn InspectDriver>> = Lazy::new(|| {
)
});
/// Stores the run driver.
///
/// This will, on load, find the best way to run containers in the
/// current environment. Once that strategy is determined,
/// it will be available for any part of the program to call
/// on to perform inspections.
///
/// # Panics
///
/// This will cause a panic if a run strategy could
/// not be determined.
static RUN_DRIVER: Lazy<Arc<dyn RunDriver>> = Lazy::new(|| {
let driver = SELECTED_RUN_DRIVER.lock().unwrap();
driver.map_or_else(
|| panic!("Driver needs to be initialized"),
|driver| -> Arc<dyn RunDriver> {
match driver {
RunDriverType::Podman => Arc::new(PodmanDriver),
RunDriverType::Docker => Arc::new(DockerDriver),
}
},
)
});
/// UUID used to mark the current builds
static BUILD_ID: Lazy<Uuid> = Lazy::new(Uuid::new_v4);
@ -218,6 +244,20 @@ pub trait BuildDriver: Sync + Send {
}
}
pub trait RunDriver: Sync + Send {
/// Run a container to perform an action.
///
/// # Errors
/// Will error if there is an issue running the container.
fn run(&self, opts: &RunOpts) -> std::io::Result<ExitStatus>;
/// Run a container to perform an action and capturing output.
///
/// # Errors
/// Will error if there is an issue running the container.
fn run_output(&self, opts: &RunOpts) -> std::io::Result<Output>;
}
/// Allows agnostic inspection of images.
pub trait InspectDriver: Sync + Send {
/// Gets the metadata on an image tag.
@ -243,6 +283,9 @@ pub struct Driver<'a> {
#[builder(default)]
inspect_driver: Option<InspectDriverType>,
#[builder(default)]
run_driver: Option<RunDriverType>,
}
impl Driver<'_> {
@ -252,35 +295,46 @@ impl Driver<'_> {
/// you will want to run init before trying to use any of
/// the strategies.
///
/// # Errors
/// Will error if it is unable to set the user credentials.
pub fn init(self) -> Result<()> {
/// # Panics
/// Will panic if it is unable to initialize drivers.
pub fn init(self) {
trace!("Driver::init()");
let init = INIT.lock().map_err(|e| anyhow!("{e}"))?;
credentials::set_user_creds(self.username, self.password, self.registry)?;
let mut initialized = INIT.lock().expect("Must lock INIT");
let mut build_driver = SELECTED_BUILD_DRIVER.lock().map_err(|e| anyhow!("{e}"))?;
let mut inspect_driver = SELECTED_INSPECT_DRIVER.lock().map_err(|e| anyhow!("{e}"))?;
if !*initialized {
credentials::set_user_creds(self.username, self.password, self.registry);
*build_driver = Some(match self.build_driver {
None => Self::determine_build_driver()?,
Some(driver) => driver,
});
trace!("Build driver set to {:?}", *build_driver);
drop(build_driver);
let _ = Self::get_build_driver();
let mut build_driver = SELECTED_BUILD_DRIVER.lock().expect("Must lock BuildDriver");
let mut inspect_driver = SELECTED_INSPECT_DRIVER
.lock()
.expect("Must lock InspectDriver");
let mut run_driver = SELECTED_RUN_DRIVER.lock().expect("Must lock RunDriver");
*inspect_driver = Some(match self.inspect_driver {
None => Self::determine_inspect_driver()?,
Some(driver) => driver,
});
trace!("Inspect driver set to {:?}", *inspect_driver);
drop(inspect_driver);
let _ = Self::get_inspection_driver();
*build_driver = Some(
self.build_driver
.map_or_else(Self::determine_build_driver, |driver| driver),
);
trace!("Build driver set to {:?}", *build_driver);
drop(build_driver);
let _ = Self::get_build_driver();
drop(init);
*inspect_driver = Some(
self.inspect_driver
.map_or_else(Self::determine_inspect_driver, |driver| driver),
);
trace!("Inspect driver set to {:?}", *inspect_driver);
drop(inspect_driver);
let _ = Self::get_inspection_driver();
Ok(())
*run_driver = Some(
self.run_driver
.map_or_else(Self::determine_run_driver, |driver| driver),
);
drop(run_driver);
let _ = Self::get_run_driver();
*initialized = true;
}
}
/// Gets the current build's UUID
@ -302,6 +356,11 @@ impl Driver<'_> {
INSPECT_DRIVER.clone()
}
pub fn get_run_driver() -> Arc<dyn RunDriver> {
trace!("Driver::get_run_driver()");
RUN_DRIVER.clone()
}
/// Retrieve the `os_version` for an image.
///
/// This gets cached for faster resolution if it's required
@ -352,10 +411,10 @@ impl Driver<'_> {
Ok(os_version)
}
fn determine_inspect_driver() -> Result<InspectDriverType> {
fn determine_inspect_driver() -> InspectDriverType {
trace!("Driver::determine_inspect_driver()");
Ok(match (
match (
blue_build_utils::check_command_exists("skopeo"),
blue_build_utils::check_command_exists("docker"),
blue_build_utils::check_command_exists("podman"),
@ -363,14 +422,14 @@ impl Driver<'_> {
(Ok(_skopeo), _, _) => InspectDriverType::Skopeo,
(_, Ok(_docker), _) => InspectDriverType::Docker,
(_, _, Ok(_podman)) => InspectDriverType::Podman,
_ => bail!("Could not determine inspection strategy. You need either skopeo, docker, or podman"),
})
_ => panic!("Could not determine inspection strategy. You need either skopeo, docker, or podman"),
}
}
fn determine_build_driver() -> Result<BuildDriverType> {
fn determine_build_driver() -> BuildDriverType {
trace!("Driver::determine_build_driver()");
Ok(match (
match (
blue_build_utils::check_command_exists("docker"),
blue_build_utils::check_command_exists("podman"),
blue_build_utils::check_command_exists("buildah"),
@ -384,12 +443,34 @@ impl Driver<'_> {
(_, _, Ok(_buildah)) if BuildahDriver::is_supported_version() => {
BuildDriverType::Buildah
}
_ => bail!(
_ => panic!(
"Could not determine strategy, need either docker version {}, podman version {}, or buildah version {} to continue",
DockerDriver::VERSION_REQ,
PodmanDriver::VERSION_REQ,
BuildahDriver::VERSION_REQ,
),
})
}
}
fn determine_run_driver() -> RunDriverType {
trace!("Driver::determine_run_driver()");
match (
blue_build_utils::check_command_exists("docker"),
blue_build_utils::check_command_exists("podman"),
) {
(Ok(_docker), _) if DockerDriver::is_supported_version() => RunDriverType::Docker,
(_, Ok(_podman)) if PodmanDriver::is_supported_version() => RunDriverType::Podman,
_ => panic!(
"{}{}{}{}",
"Could not determine strategy, ",
format_args!("need either docker version {}, ", DockerDriver::VERSION_REQ),
format_args!("podman version {}, ", PodmanDriver::VERSION_REQ),
format_args!(
"or buildah version {} to continue",
BuildahDriver::VERSION_REQ
),
),
}
}
}

View file

@ -1,22 +1,32 @@
use std::{env, process::Command, sync::Mutex, time::Duration};
use std::{
env,
path::Path,
process::{Command, ExitStatus},
sync::Mutex,
time::Duration,
};
use anyhow::{anyhow, bail, Result};
use blue_build_utils::{
constants::{BB_BUILDKIT_CACHE_GHA, CONTAINER_FILE, DOCKER_HOST, SKOPEO_IMAGE},
logging::{CommandLogging, Logger},
signal_handler::{add_cid, remove_cid, ContainerId},
};
use indicatif::{ProgressBar, ProgressStyle};
use log::{info, trace, warn};
use once_cell::sync::Lazy;
use semver::Version;
use serde::Deserialize;
use tempdir::TempDir;
use crate::{credentials::Credentials, image_metadata::ImageMetadata};
use crate::{
credentials::Credentials, drivers::types::RunDriverType, image_metadata::ImageMetadata,
};
use super::{
credentials,
opts::{BuildOpts, BuildTagPushOpts, GetMetadataOpts, PushOpts, TagOpts},
BuildDriver, DriverVersion, InspectDriver,
opts::{BuildOpts, BuildTagPushOpts, GetMetadataOpts, PushOpts, RunOpts, TagOpts},
BuildDriver, DriverVersion, InspectDriver, RunDriver,
};
#[derive(Debug, Deserialize)]
@ -308,14 +318,12 @@ impl InspectDriver for DockerDriver {
);
progress.enable_steady_tick(Duration::from_millis(100));
trace!("docker run {SKOPEO_IMAGE} inspect {url}");
let output = Command::new("docker")
.arg("run")
.arg("--rm")
.arg(SKOPEO_IMAGE)
.arg("inspect")
.arg(&url)
.output()?;
let output = self.run_output(
&RunOpts::builder()
.image(SKOPEO_IMAGE)
.args(&["inspect".to_string(), url.clone()])
.build(),
)?;
progress.finish();
Logger::multi_progress().remove(&progress);
@ -329,3 +337,78 @@ impl InspectDriver for DockerDriver {
Ok(serde_json::from_slice(&output.stdout)?)
}
}
impl RunDriver for DockerDriver {
fn run(&self, opts: &RunOpts) -> std::io::Result<ExitStatus> {
trace!("DockerDriver::run({opts:#?})");
let cid_path = TempDir::new("docker")?;
let cid_file = cid_path.path().join("cid");
let cid = ContainerId::new(&cid_file, RunDriverType::Docker, false);
add_cid(&cid);
let status = docker_run(opts, &cid_file)
.status_image_ref_progress(opts.image.as_ref(), "Running container")?;
remove_cid(&cid);
Ok(status)
}
fn run_output(&self, opts: &RunOpts) -> std::io::Result<std::process::Output> {
trace!("DockerDriver::run({opts:#?})");
let cid_path = TempDir::new("docker")?;
let cid_file = cid_path.path().join("cid");
let cid = ContainerId::new(&cid_file, RunDriverType::Docker, false);
add_cid(&cid);
let output = docker_run(opts, &cid_file).output()?;
remove_cid(&cid);
Ok(output)
}
}
fn docker_run(opts: &RunOpts, cid_file: &Path) -> Command {
let mut command = Command::new("docker");
command
.arg("run")
.arg(format!("--cidfile={}", cid_file.display()));
if opts.privileged {
command.arg("--privileged");
}
if opts.remove {
command.arg("--rm");
}
if opts.pull {
command.arg("--pull=always");
}
opts.volumes.iter().for_each(|volume| {
command.arg("--volume");
command.arg(format!(
"{}:{}",
volume.path_or_vol_name, volume.container_path,
));
});
opts.env_vars.iter().for_each(|env| {
command.arg("--env");
command.arg(format!("{}={}", env.key, env.value));
});
command.arg(opts.image.as_ref());
command.args(opts.args.iter());
trace!("{command:?}");
command
}

View file

@ -2,9 +2,11 @@ use clap::ValueEnum;
pub use build::*;
pub use inspect::*;
pub use run::*;
mod build;
mod inspect;
mod run;
#[derive(Debug, Copy, Clone, Default, ValueEnum)]
pub enum CompressionType {

45
src/drivers/opts/run.rs Normal file
View file

@ -0,0 +1,45 @@
use std::borrow::Cow;
use typed_builder::TypedBuilder;
#[derive(Debug, Clone, TypedBuilder)]
pub struct RunOpts<'a> {
#[builder(default, setter(into))]
pub image: Cow<'a, str>,
#[builder(default, setter(into))]
pub args: Cow<'a, [String]>,
#[builder(default, setter(into))]
pub env_vars: Cow<'a, [RunOptsEnv<'a>]>,
#[builder(default, setter(into))]
pub volumes: Cow<'a, [RunOptsVolume<'a>]>,
#[builder(default)]
pub privileged: bool,
#[builder(default)]
pub pull: bool,
#[builder(default)]
pub remove: bool,
}
#[derive(Debug, Clone, TypedBuilder)]
pub struct RunOptsVolume<'a> {
#[builder(setter(into))]
pub path_or_vol_name: Cow<'a, str>,
#[builder(setter(into))]
pub container_path: Cow<'a, str>,
}
#[derive(Debug, Clone, TypedBuilder)]
pub struct RunOptsEnv<'a> {
#[builder(setter(into))]
pub key: Cow<'a, str>,
#[builder(setter(into))]
pub value: Cow<'a, str>,
}

View file

@ -1,21 +1,30 @@
use std::{process::Command, time::Duration};
use std::{
path::Path,
process::{Command, ExitStatus},
time::Duration,
};
use anyhow::{bail, Result};
use blue_build_utils::{
constants::SKOPEO_IMAGE,
logging::{CommandLogging, Logger},
signal_handler::{add_cid, remove_cid, ContainerId},
};
use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use log::{debug, error, info, trace};
use log::{debug, error, info, trace, warn};
use semver::Version;
use serde::Deserialize;
use tempdir::TempDir;
use crate::{credentials::Credentials, image_metadata::ImageMetadata};
use crate::{
credentials::Credentials, drivers::types::RunDriverType, image_metadata::ImageMetadata,
};
use super::{
credentials,
opts::{BuildOpts, GetMetadataOpts, PushOpts, TagOpts},
BuildDriver, DriverVersion, InspectDriver,
opts::{BuildOpts, GetMetadataOpts, PushOpts, RunOpts, TagOpts},
BuildDriver, DriverVersion, InspectDriver, RunDriver,
};
#[derive(Debug, Deserialize)]
@ -170,14 +179,12 @@ impl InspectDriver for PodmanDriver {
);
progress.enable_steady_tick(Duration::from_millis(100));
trace!("podman run {SKOPEO_IMAGE} inspect {url}");
let output = Command::new("podman")
.arg("run")
.arg("--rm")
.arg(SKOPEO_IMAGE)
.arg("inspect")
.arg(&url)
.output()?;
let output = self.run_output(
&RunOpts::builder()
.image(SKOPEO_IMAGE)
.args(&["inspect".to_string(), url.clone()])
.build(),
)?;
progress.finish();
Logger::multi_progress().remove(&progress);
@ -190,3 +197,91 @@ impl InspectDriver for PodmanDriver {
Ok(serde_json::from_slice(&output.stdout)?)
}
}
impl RunDriver for PodmanDriver {
fn run(&self, opts: &RunOpts) -> std::io::Result<ExitStatus> {
trace!("PodmanDriver::run({opts:#?})");
let cid_path = TempDir::new("podman")?;
let cid_file = cid_path.path().join("cid");
let cid = ContainerId::new(&cid_file, RunDriverType::Podman, opts.privileged);
add_cid(&cid);
let status = podman_run(opts, &cid_file)
.status_image_ref_progress(opts.image.as_ref(), "Running container")?;
remove_cid(&cid);
Ok(status)
}
fn run_output(&self, opts: &RunOpts) -> std::io::Result<std::process::Output> {
trace!("PodmanDriver::run_output({opts:#?})");
let cid_path = TempDir::new("podman")?;
let cid_file = cid_path.path().join("cid");
let cid = ContainerId::new(&cid_file, RunDriverType::Podman, opts.privileged);
add_cid(&cid);
let output = podman_run(opts, &cid_file).output()?;
remove_cid(&cid);
Ok(output)
}
}
fn podman_run(opts: &RunOpts, cid_file: &Path) -> Command {
let mut command = if opts.privileged {
warn!(
"Running 'podman' in privileged mode requires '{}'",
"sudo".bold().red()
);
Command::new("sudo")
} else {
Command::new("podman")
};
if opts.privileged {
command.arg("podman");
}
command
.arg("run")
.arg(format!("--cidfile={}", cid_file.display()));
if opts.privileged {
command.arg("--privileged");
}
if opts.remove {
command.arg("--rm");
}
if opts.pull {
command.arg("--pull=always");
}
opts.volumes.iter().for_each(|volume| {
command.arg("--volume");
command.arg(format!(
"{}:{}",
volume.path_or_vol_name, volume.container_path,
));
});
opts.env_vars.iter().for_each(|env| {
command.arg("--env");
command.arg(format!("{}={}", env.key, env.value));
});
command.arg(opts.image.as_ref());
command.args(opts.args.iter());
trace!("{command:?}");
command
}

View file

@ -13,3 +13,18 @@ pub enum BuildDriverType {
Podman,
Docker,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum RunDriverType {
Podman,
Docker,
}
impl From<RunDriverType> for String {
fn from(value: RunDriverType) -> Self {
match value {
RunDriverType::Podman => "podman".to_string(),
RunDriverType::Docker => "docker".to_string(),
}
}
}

View file

@ -32,6 +32,7 @@ indicatif.workspace = true
indicatif-log-bridge.workspace = true
log.workspace = true
once_cell.workspace = true
tempdir.workspace = true
serde.workspace = true
serde_yaml.workspace = true
serde_json.workspace = true

View file

@ -3,7 +3,7 @@ use std::{
fs::OpenOptions,
io::{BufRead, BufReader, Result, Write as IoWrite},
path::{Path, PathBuf},
process::{Command, ExitStatus},
process::{Command, ExitStatus, Stdio},
sync::{Arc, Mutex},
thread,
time::Duration,
@ -199,7 +199,9 @@ impl CommandLogging for Command {
let log_prefix = Arc::new(log_header(short_name));
let (reader, writer) = os_pipe::pipe()?;
self.stdout(writer.try_clone()?).stderr(writer);
self.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())));

View file

@ -1,10 +1,12 @@
use std::{
process,
fs,
path::PathBuf,
process::{self, Command},
sync::{atomic::AtomicBool, Arc, Mutex},
thread,
};
use log::{error, trace, warn};
use log::{debug, error, trace, warn};
use nix::{
libc::{SIGABRT, SIGCONT, SIGHUP, SIGTSTP},
sys::signal::{kill, Signal},
@ -20,7 +22,31 @@ use signal_hook::{
use crate::logging::Logger;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContainerId {
cid_path: PathBuf,
requires_sudo: bool,
crt: String,
}
impl ContainerId {
pub fn new<P, S>(cid_path: P, container_runtime: S, requires_sudo: bool) -> Self
where
P: Into<PathBuf>,
S: Into<String>,
{
let cid_path = cid_path.into();
let crt = container_runtime.into();
Self {
cid_path,
requires_sudo,
crt,
}
}
}
static PID_LIST: Lazy<Arc<Mutex<Vec<i32>>>> = Lazy::new(|| Arc::new(Mutex::new(vec![])));
static CID_LIST: Lazy<Arc<Mutex<Vec<ContainerId>>>> = Lazy::new(|| Arc::new(Mutex::new(vec![])));
/// Initialize Ctrl-C handler. This should be done at the start
/// of a binary.
@ -57,10 +83,36 @@ where
warn!("Received termination signal, cleaning up...");
trace!("{info:#?}");
Logger::multi_progress().clear().unwrap();
Logger::multi_progress()
.clear()
.expect("Should clear multi_progress");
send_signal_processes(termsig);
let cid_list = CID_LIST.clone();
let cid_list = cid_list.lock().expect("Should lock mutex");
cid_list.iter().for_each(|cid| {
if let Ok(id) = fs::read_to_string(&cid.cid_path) {
let id = id.trim();
debug!("Killing container {id}");
let status = if cid.requires_sudo {
Command::new("sudo")
.arg(&cid.crt)
.arg("stop")
.arg(id)
.status()
} else {
Command::new(&cid.crt).arg("stop").arg(id).status()
};
if let Err(e) = status {
error!("Failed to kill container {id}: Error {e}");
}
}
});
drop(cid_list);
process::exit(1);
}
SIGTSTP => {
@ -86,8 +138,12 @@ where
fn send_signal_processes(sig: i32) {
let pid_list = PID_LIST.clone();
let pid_list = pid_list.lock().expect("Should lock mutex");
pid_list.iter().for_each(|pid| {
if let Err(e) = kill(Pid::from_raw(*pid), Signal::try_from(sig).unwrap()) {
if let Err(e) = kill(
Pid::from_raw(*pid),
Signal::try_from(sig).expect("Should be valid signal"),
) {
error!("Failed to kill process {pid}: Error {e}");
} else {
trace!("Killed process {pid}");
@ -130,3 +186,28 @@ where
}
}
}
/// Add a cid to the list to kill when the program
/// recieves a kill signal.
///
/// # Panics
/// Will panic if the mutex cannot be locked.
pub fn add_cid(cid: &ContainerId) {
let mut cid_list = CID_LIST.lock().expect("Should lock cid_list");
if !cid_list.contains(cid) {
cid_list.push(cid.clone());
}
}
/// Remove a cid from the list of pids to kill.
///
/// # Panics
/// Will panic if the mutex cannot be locked.
pub fn remove_cid(cid: &ContainerId) {
let mut cid_list = CID_LIST.lock().expect("Should lock cid_list");
if let Some(index) = cid_list.iter().position(|val| *val == *cid) {
cid_list.swap_remove(index);
}
}