mod docker;
mod fake;
use std::process::Command;
use ci_config::Service;
pub use docker::DockerProvider;
pub use fake::FakeProvider;
use thiserror::Error;
#[derive(Debug, Default)]
pub struct RunningServices {
pub handles: Vec<String>,
}
#[derive(Debug, Error)]
pub enum ServiceError {
#[error(
"service container {name:?} ({image}) requested, but this provider cannot run services"
)]
Unsupported {
name: String,
image: String,
},
#[error("service provisioning failed: {0}")]
Provision(String),
#[error("service {name:?} ({image}) did not become ready within {timeout_secs}s")]
NotReady {
name: String,
image: String,
timeout_secs: u64,
},
}
pub trait ServiceProvider {
fn up(&self, services: &[Service]) -> Result<RunningServices, ServiceError>;
fn down(&self, running: RunningServices) -> Result<(), ServiceError>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoopProvider;
impl ServiceProvider for NoopProvider {
fn up(&self, services: &[Service]) -> Result<RunningServices, ServiceError> {
match services.first() {
Some(service) => Err(ServiceError::Unsupported {
name: service.name.clone(),
image: service.image.clone(),
}),
None => Ok(RunningServices::default()),
}
}
fn down(&self, _running: RunningServices) -> Result<(), ServiceError> {
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandOutcome {
pub success: bool,
pub output: String,
}
pub trait CommandRunner: Send + Sync {
fn run(&self, program: &str, args: &[String]) -> Result<CommandOutcome, ServiceError>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct RealCommandRunner;
impl CommandRunner for RealCommandRunner {
fn run(&self, program: &str, args: &[String]) -> Result<CommandOutcome, ServiceError> {
let output = Command::new(program)
.args(args)
.output()
.map_err(|error| ServiceError::Provision(format!("spawn {program}: {error}")))?;
let mut text = String::from_utf8_lossy(&output.stdout).into_owned();
text.push_str(&String::from_utf8_lossy(&output.stderr));
Ok(CommandOutcome {
success: output.status.success(),
output: text,
})
}
}