1mod docker;
5mod fake;
6
7use std::process::Command;
8
9use ci_config::Service;
10pub use docker::DockerProvider;
11pub use fake::FakeProvider;
12use thiserror::Error;
13
14#[derive(Debug, Default)]
16pub struct RunningServices {
17 pub handles: Vec<String>,
19}
20
21#[derive(Debug, Error)]
23pub enum ServiceError {
24 #[error(
26 "service container {name:?} ({image}) requested, but this provider cannot run services"
27 )]
28 Unsupported {
29 name: String,
31 image: String,
33 },
34 #[error("service provisioning failed: {0}")]
36 Provision(String),
37 #[error("service {name:?} ({image}) did not become ready within {timeout_secs}s")]
39 NotReady {
40 name: String,
42 image: String,
44 timeout_secs: u64,
46 },
47}
48
49pub trait ServiceProvider {
51 fn up(&self, services: &[Service]) -> Result<RunningServices, ServiceError>;
53 fn down(&self, running: RunningServices) -> Result<(), ServiceError>;
55}
56
57#[derive(Debug, Clone, Copy, Default)]
59pub struct NoopProvider;
60
61impl ServiceProvider for NoopProvider {
62 fn up(&self, services: &[Service]) -> Result<RunningServices, ServiceError> {
63 match services.first() {
64 Some(service) => Err(ServiceError::Unsupported {
65 name: service.name.clone(),
66 image: service.image.clone(),
67 }),
68 None => Ok(RunningServices::default()),
69 }
70 }
71
72 fn down(&self, _running: RunningServices) -> Result<(), ServiceError> {
73 Ok(())
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct CommandOutcome {
80 pub success: bool,
82 pub output: String,
84}
85
86pub trait CommandRunner: Send + Sync {
88 fn run(&self, program: &str, args: &[String]) -> Result<CommandOutcome, ServiceError>;
90}
91
92#[derive(Debug, Clone, Copy, Default)]
94pub struct RealCommandRunner;
95
96impl CommandRunner for RealCommandRunner {
97 fn run(&self, program: &str, args: &[String]) -> Result<CommandOutcome, ServiceError> {
98 let output = Command::new(program)
99 .args(args)
100 .output()
101 .map_err(|error| ServiceError::Provision(format!("spawn {program}: {error}")))?;
102 let mut text = String::from_utf8_lossy(&output.stdout).into_owned();
103 text.push_str(&String::from_utf8_lossy(&output.stderr));
104 Ok(CommandOutcome {
105 success: output.status.success(),
106 output: text,
107 })
108 }
109}