pub mod canary;
pub mod parallel;
pub mod rolling;
pub mod sequential;
use std::time::Instant;
use async_trait::async_trait;
use chrono::Utc;
use tracing::{error, info};
pub use canary::CanaryStrategy;
pub use parallel::ParallelStrategy;
pub use rolling::RollingStrategy;
pub use sequential::SequentialStrategy;
use crate::connection::DatabaseClient;
use crate::error::Result;
use crate::migration::{execute_migration, MigrationDirection};
use crate::orchestration::coordinator::DeploymentPlan;
use crate::orchestration::environment::EnvironmentConfig;
use crate::orchestration::result::{DeploymentResult, DeploymentStatus};
#[async_trait]
pub trait DeploymentStrategy: std::fmt::Debug + Send + Sync {
async fn deploy(&self, plan: &DeploymentPlan) -> Result<Vec<DeploymentResult>>;
}
pub async fn deploy_to_environment(
env: &EnvironmentConfig,
plan: &DeploymentPlan,
) -> DeploymentResult {
let started_at = Utc::now();
if plan.dry_run {
info!(environment = %env.name, "dry_run_deployment");
return DeploymentResult::builder(&env.name, DeploymentStatus::Success, started_at)
.completed_at(Utc::now())
.execution_time_ms(0)
.migrations_applied(plan.migrations.len())
.build();
}
info!(
environment = %env.name,
migrations = plan.migrations.len(),
"deploying_to_environment"
);
let client = match DatabaseClient::new(env.connection.clone()) {
Ok(client) => client,
Err(err) => {
error!(environment = %env.name, error = %err, "deployment_client_failed");
return DeploymentResult::builder(&env.name, DeploymentStatus::Failed, started_at)
.completed_at(Utc::now())
.error(err.to_string())
.build();
}
};
if let Err(err) = client.connect().await {
error!(environment = %env.name, error = %err, "deployment_connect_failed");
return DeploymentResult::builder(&env.name, DeploymentStatus::Failed, started_at)
.completed_at(Utc::now())
.error(err.to_string())
.build();
}
let start = Instant::now();
let mut applied = 0usize;
for migration in &plan.migrations {
if let Err(err) = execute_migration(&client, migration, MigrationDirection::Up).await {
error!(environment = %env.name, migration = %migration.version, error = %err, "deployment_failed");
let _ = client.disconnect().await;
return DeploymentResult::builder(&env.name, DeploymentStatus::Failed, started_at)
.completed_at(Utc::now())
.error(err.to_string())
.migrations_applied(applied)
.build();
}
applied += 1;
}
let elapsed_ms = u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX);
let _ = client.disconnect().await;
info!(
environment = %env.name,
execution_time_ms = elapsed_ms,
"deployment_successful"
);
DeploymentResult::builder(&env.name, DeploymentStatus::Success, started_at)
.completed_at(Utc::now())
.execution_time_ms(elapsed_ms)
.migrations_applied(applied)
.build()
}
pub async fn resolve_plan_environments(plan: &DeploymentPlan) -> Result<Vec<EnvironmentConfig>> {
let registry = plan.registry.clone();
let mut out = Vec::with_capacity(plan.environments.len());
for name in &plan.environments {
match registry.get(name).await {
Some(cfg) => out.push(cfg),
None => {
return Err(crate::error::SurqlError::Orchestration {
reason: format!("Environment not found: {name}"),
});
}
}
}
Ok(out)
}