2026-03-02 11:19:55 +01:00
|
|
|
mod attach_provider;
|
2025-12-10 10:11:45 +01:00
|
|
|
pub mod clients;
|
|
|
|
|
pub mod orchestrator;
|
|
|
|
|
pub mod ports;
|
|
|
|
|
pub mod readiness;
|
|
|
|
|
pub mod setup;
|
2025-12-01 12:48:39 +01:00
|
|
|
|
2026-03-06 14:08:56 +01:00
|
|
|
use std::marker::PhantomData;
|
2026-02-02 07:19:22 +01:00
|
|
|
|
2025-12-01 12:48:39 +01:00
|
|
|
use async_trait::async_trait;
|
2025-12-10 10:11:45 +01:00
|
|
|
use testing_framework_core::scenario::{
|
2026-03-06 13:31:03 +01:00
|
|
|
AttachSource, CleanupGuard, Deployer, DynError, FeedHandle, ObservabilityCapabilityProvider,
|
|
|
|
|
RequiresNodeControl, Runner, Scenario,
|
2025-12-01 12:48:39 +01:00
|
|
|
};
|
|
|
|
|
|
2026-02-02 07:19:22 +01:00
|
|
|
use crate::{env::ComposeDeployEnv, errors::ComposeRunnerError, lifecycle::cleanup::RunnerCleanup};
|
2025-12-01 12:48:39 +01:00
|
|
|
|
2026-02-02 07:19:22 +01:00
|
|
|
/// Docker Compose-based deployer for test scenarios.
|
2025-12-01 12:48:39 +01:00
|
|
|
#[derive(Clone, Copy)]
|
2026-02-02 07:19:22 +01:00
|
|
|
pub struct ComposeDeployer<E: ComposeDeployEnv> {
|
2025-12-01 12:48:39 +01:00
|
|
|
readiness_checks: bool,
|
2026-02-02 07:19:22 +01:00
|
|
|
_env: PhantomData<E>,
|
2025-12-01 12:48:39 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-06 13:25:11 +01:00
|
|
|
/// Compose deployment metadata returned by compose-specific deployment APIs.
|
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
|
pub struct ComposeDeploymentMetadata {
|
|
|
|
|
/// Docker Compose project name used for this deployment when available.
|
|
|
|
|
pub project_name: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 13:31:03 +01:00
|
|
|
impl ComposeDeploymentMetadata {
|
|
|
|
|
/// Returns project name when deployment is bound to a specific compose
|
|
|
|
|
/// project.
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub fn project_name(&self) -> Option<&str> {
|
|
|
|
|
self.project_name.as_deref()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Builds an attach source for the same compose project.
|
|
|
|
|
pub fn attach_source_for_services(
|
|
|
|
|
&self,
|
|
|
|
|
services: Vec<String>,
|
|
|
|
|
) -> Result<AttachSource, DynError> {
|
|
|
|
|
let Some(project_name) = self.project_name() else {
|
|
|
|
|
return Err("compose metadata has no project name".into());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(AttachSource::compose(services).with_project(project_name.to_owned()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 07:19:22 +01:00
|
|
|
impl<E: ComposeDeployEnv> Default for ComposeDeployer<E> {
|
2025-12-01 12:48:39 +01:00
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 07:19:22 +01:00
|
|
|
impl<E: ComposeDeployEnv> ComposeDeployer<E> {
|
2025-12-01 12:48:39 +01:00
|
|
|
#[must_use]
|
|
|
|
|
pub const fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
readiness_checks: true,
|
2026-02-02 07:19:22 +01:00
|
|
|
_env: PhantomData,
|
2025-12-01 12:48:39 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub const fn with_readiness(mut self, enabled: bool) -> Self {
|
|
|
|
|
self.readiness_checks = enabled;
|
|
|
|
|
self
|
|
|
|
|
}
|
2026-03-06 13:25:11 +01:00
|
|
|
|
|
|
|
|
/// Deploy and return compose-specific metadata alongside the generic
|
|
|
|
|
/// runner.
|
|
|
|
|
pub async fn deploy_with_metadata<Caps>(
|
|
|
|
|
&self,
|
|
|
|
|
scenario: &Scenario<E, Caps>,
|
|
|
|
|
) -> Result<(Runner<E>, ComposeDeploymentMetadata), ComposeRunnerError>
|
|
|
|
|
where
|
|
|
|
|
Caps: RequiresNodeControl + ObservabilityCapabilityProvider + Send + Sync,
|
|
|
|
|
{
|
|
|
|
|
let deployer = Self {
|
|
|
|
|
readiness_checks: self.readiness_checks,
|
|
|
|
|
_env: PhantomData,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
orchestrator::DeploymentOrchestrator::new(deployer)
|
|
|
|
|
.deploy_with_metadata(scenario)
|
|
|
|
|
.await
|
|
|
|
|
}
|
2025-12-01 12:48:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
2026-02-02 07:19:22 +01:00
|
|
|
impl<E, Caps> Deployer<E, Caps> for ComposeDeployer<E>
|
2025-12-01 12:48:39 +01:00
|
|
|
where
|
2025-12-17 18:28:36 +01:00
|
|
|
Caps: RequiresNodeControl + ObservabilityCapabilityProvider + Send + Sync,
|
2026-02-02 07:19:22 +01:00
|
|
|
E: ComposeDeployEnv,
|
2025-12-01 12:48:39 +01:00
|
|
|
{
|
|
|
|
|
type Error = ComposeRunnerError;
|
|
|
|
|
|
2026-02-02 07:19:22 +01:00
|
|
|
async fn deploy(&self, scenario: &Scenario<E, Caps>) -> Result<Runner<E>, Self::Error> {
|
|
|
|
|
let deployer = Self {
|
|
|
|
|
readiness_checks: self.readiness_checks,
|
|
|
|
|
_env: PhantomData,
|
|
|
|
|
};
|
|
|
|
|
orchestrator::DeploymentOrchestrator::new(deployer)
|
2025-12-10 10:11:45 +01:00
|
|
|
.deploy(scenario)
|
|
|
|
|
.await
|
2025-12-01 12:48:39 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-10 10:11:45 +01:00
|
|
|
pub(super) struct ComposeCleanupGuard {
|
2025-12-01 12:48:39 +01:00
|
|
|
environment: RunnerCleanup,
|
2026-02-02 07:19:22 +01:00
|
|
|
block_feed: Option<FeedHandle>,
|
2025-12-01 12:48:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ComposeCleanupGuard {
|
2026-02-02 07:19:22 +01:00
|
|
|
const fn new(environment: RunnerCleanup, block_feed: FeedHandle) -> Self {
|
2025-12-01 12:48:39 +01:00
|
|
|
Self {
|
|
|
|
|
environment,
|
|
|
|
|
block_feed: Some(block_feed),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CleanupGuard for ComposeCleanupGuard {
|
|
|
|
|
fn cleanup(mut self: Box<Self>) {
|
|
|
|
|
if let Some(block_feed) = self.block_feed.take() {
|
|
|
|
|
CleanupGuard::cleanup(Box::new(block_feed));
|
|
|
|
|
}
|
|
|
|
|
CleanupGuard::cleanup(Box::new(self.environment));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-10 10:11:45 +01:00
|
|
|
pub(super) fn make_cleanup_guard(
|
|
|
|
|
environment: RunnerCleanup,
|
2026-02-02 07:19:22 +01:00
|
|
|
block_feed: FeedHandle,
|
2025-12-10 10:11:45 +01:00
|
|
|
) -> Box<dyn CleanupGuard> {
|
|
|
|
|
Box::new(ComposeCleanupGuard::new(environment, block_feed))
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-01 12:48:39 +01:00
|
|
|
#[cfg(test)]
|
2026-02-02 07:19:22 +01:00
|
|
|
mod tests {}
|