2025-12-10 10:11:45 +01:00
|
|
|
use std::{
|
|
|
|
|
env,
|
|
|
|
|
net::{Ipv4Addr, TcpListener as StdTcpListener},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use testing_framework_core::topology::generation::GeneratedTopology;
|
2025-12-11 10:08:49 +01:00
|
|
|
use tracing::{debug, info};
|
2025-12-10 10:11:45 +01:00
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
|
docker::ensure_docker_available,
|
2025-12-10 15:26:34 +01:00
|
|
|
errors::ComposeRunnerError,
|
|
|
|
|
infrastructure::environment::{
|
2025-12-10 10:11:45 +01:00
|
|
|
PortReservation, StackEnvironment, ensure_supported_topology, prepare_environment,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub const PROMETHEUS_PORT_ENV: &str = "TEST_FRAMEWORK_PROMETHEUS_PORT";
|
|
|
|
|
pub const DEFAULT_PROMETHEUS_PORT: u16 = 9090;
|
|
|
|
|
|
|
|
|
|
pub struct DeploymentSetup {
|
|
|
|
|
descriptors: GeneratedTopology,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct DeploymentContext {
|
|
|
|
|
pub descriptors: GeneratedTopology,
|
|
|
|
|
pub environment: StackEnvironment,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DeploymentSetup {
|
|
|
|
|
pub fn new(descriptors: &GeneratedTopology) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
descriptors: descriptors.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn validate_environment(&self) -> Result<(), ComposeRunnerError> {
|
|
|
|
|
ensure_docker_available().await?;
|
|
|
|
|
ensure_supported_topology(&self.descriptors)?;
|
|
|
|
|
|
|
|
|
|
info!(
|
|
|
|
|
validators = self.descriptors.validators().len(),
|
|
|
|
|
executors = self.descriptors.executors().len(),
|
|
|
|
|
"starting compose deployment"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn prepare_workspace(self) -> Result<DeploymentContext, ComposeRunnerError> {
|
|
|
|
|
let prometheus_env = env::var(PROMETHEUS_PORT_ENV)
|
|
|
|
|
.ok()
|
|
|
|
|
.and_then(|raw| raw.parse::<u16>().ok());
|
|
|
|
|
if prometheus_env.is_some() {
|
|
|
|
|
info!(port = prometheus_env, "using prometheus port from env");
|
|
|
|
|
}
|
2025-12-15 22:29:36 +01:00
|
|
|
|
2025-12-10 10:11:45 +01:00
|
|
|
let prometheus_port = prometheus_env
|
|
|
|
|
.and_then(|port| reserve_port(port))
|
|
|
|
|
.or_else(|| allocate_prometheus_port())
|
|
|
|
|
.unwrap_or_else(|| PortReservation::new(DEFAULT_PROMETHEUS_PORT, None));
|
2025-12-15 22:29:36 +01:00
|
|
|
|
2025-12-11 10:08:49 +01:00
|
|
|
debug!(
|
|
|
|
|
prometheus_port = prometheus_port.port(),
|
|
|
|
|
"selected prometheus port"
|
|
|
|
|
);
|
2025-12-15 22:29:36 +01:00
|
|
|
|
2025-12-10 10:11:45 +01:00
|
|
|
let environment =
|
|
|
|
|
prepare_environment(&self.descriptors, prometheus_port, prometheus_env.is_some())
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
info!(
|
|
|
|
|
compose_file = %environment.compose_path().display(),
|
|
|
|
|
project = environment.project_name(),
|
|
|
|
|
root = %environment.root().display(),
|
|
|
|
|
"compose workspace prepared"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Ok(DeploymentContext {
|
|
|
|
|
descriptors: self.descriptors,
|
|
|
|
|
environment,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn allocate_prometheus_port() -> Option<PortReservation> {
|
|
|
|
|
reserve_port(DEFAULT_PROMETHEUS_PORT).or_else(|| reserve_port(0))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn reserve_port(port: u16) -> Option<PortReservation> {
|
|
|
|
|
let listener = StdTcpListener::bind((Ipv4Addr::LOCALHOST, port)).ok()?;
|
|
|
|
|
let actual_port = listener.local_addr().ok()?.port();
|
|
|
|
|
Some(PortReservation::new(actual_port, Some(listener)))
|
|
|
|
|
}
|