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:
parent
1a348f8137
commit
784be9869a
16 changed files with 510 additions and 98 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -304,6 +304,7 @@ dependencies = [
|
|||
"serde_yaml 0.9.34+deprecated",
|
||||
"signal-hook",
|
||||
"syntect",
|
||||
"tempdir",
|
||||
"typed-builder",
|
||||
"which",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:?}");
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
151
src/drivers.rs
151
src/drivers.rs
|
|
@ -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
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
45
src/drivers/opts/run.rs
Normal 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>,
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue