#[derive(Debug, Clone)]
pub struct DeploymentConfig {
pub strategy: String,
pub service_name: String,
pub environment: String,
pub current_version: String,
pub next_version: String,
pub parameters: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct DeploymentStatus {
pub success: bool,
pub message: String,
pub current_version: String,
pub previous_version: Option<String>,
pub traffic_split: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct BlueGreenDeployment {
pub service_name: String,
pub environment: String,
pub current_version: String,
pub next_version: String,
pub pre_hook: Option<Vec<String>>,
pub post_hook: Option<Vec<String>>,
pub rollback_on_failure: bool,
}
impl BlueGreenDeployment {
pub fn new(
service_name: String,
environment: String,
current_version: String,
next_version: String,
) -> Self {
Self {
service_name,
environment,
current_version,
next_version,
pre_hook: None,
post_hook: None,
rollback_on_failure: true,
}
}
pub fn pre_hook(mut self, hook: Vec<String>) -> Self {
self.pre_hook = Some(hook);
self
}
pub fn post_hook(mut self, hook: Vec<String>) -> Self {
self.post_hook = Some(hook);
self
}
pub fn rollback_on_failure(mut self, rollback: bool) -> Self {
self.rollback_on_failure = rollback;
self
}
pub fn execute(&self) -> Result<DeploymentStatus, Error> {
println!(
"Executing Blue-Green deployment for {}/{}: {} -> {}",
self.service_name, self.environment, self.current_version, self.next_version
);
Ok(DeploymentStatus {
success: true,
message: format!(
"Blue-Green deployment completed successfully for {}/{}",
self.service_name, self.environment
),
current_version: self.next_version.clone(),
previous_version: Some(self.current_version.clone()),
traffic_split: None,
})
}
}
#[derive(Debug, Clone)]
pub struct CanaryDeployment {
pub service_name: String,
pub environment: String,
pub current_version: String,
pub next_version: String,
pub traffic_split: u32,
pub auto_promote: bool,
pub promotion_threshold: u32,
pub health_check_interval: u32,
pub rollback_on_failure: bool,
}
impl CanaryDeployment {
pub fn new(
service_name: String,
environment: String,
current_version: String,
next_version: String,
traffic_split: u32,
) -> Self {
Self {
service_name,
environment,
current_version,
next_version,
traffic_split,
auto_promote: false,
promotion_threshold: 95,
health_check_interval: 30,
rollback_on_failure: true,
}
}
pub fn auto_promote(mut self, auto: bool) -> Self {
self.auto_promote = auto;
self
}
pub fn promotion_threshold(mut self, threshold: u32) -> Self {
self.promotion_threshold = threshold;
self
}
pub fn health_check_interval(mut self, interval: u32) -> Self {
self.health_check_interval = interval;
self
}
pub fn rollback_on_failure(mut self, rollback: bool) -> Self {
self.rollback_on_failure = rollback;
self
}
pub fn execute(&self) -> Result<DeploymentStatus, Error> {
println!(
"Executing Canary deployment for {}/{}: {} -> {} ({}% traffic)",
self.service_name,
self.environment,
self.current_version,
self.next_version,
self.traffic_split
);
Ok(DeploymentStatus {
success: true,
message: format!(
"Canary deployment started for {}/{} with {}% traffic",
self.service_name, self.environment, self.traffic_split
),
current_version: self.current_version.clone(),
previous_version: None,
traffic_split: Some(self.traffic_split),
})
}
pub fn promote_canary(&self) -> Result<DeploymentStatus, Error> {
println!(
"Promoting Canary deployment for {}/{} to 100% traffic",
self.service_name, self.environment
);
Ok(DeploymentStatus {
success: true,
message: format!(
"Canary promoted to full deployment for {}/{}",
self.service_name, self.environment
),
current_version: self.next_version.clone(),
previous_version: Some(self.current_version.clone()),
traffic_split: Some(100),
})
}
pub fn rollback_canary(&self) -> Result<DeploymentStatus, Error> {
println!(
"Rolling back Canary deployment for {}/{}",
self.service_name, self.environment
);
Ok(DeploymentStatus {
success: true,
message: format!(
"Canary deployment rolled back for {}/{}",
self.service_name, self.environment
),
current_version: self.current_version.clone(),
previous_version: Some(self.next_version.clone()),
traffic_split: Some(0),
})
}
}
#[derive(Debug, Clone)]
pub struct RollingDeployment {
pub service_name: String,
pub environment: String,
pub current_version: String,
pub next_version: String,
pub max_unavailable: u32,
pub max_surge: u32,
pub min_ready_seconds: u32,
pub progress_deadline_seconds: u32,
pub rollback_on_failure: bool,
}
impl RollingDeployment {
pub fn new(
service_name: String,
environment: String,
current_version: String,
next_version: String,
) -> Self {
Self {
service_name,
environment,
current_version,
next_version,
max_unavailable: 1,
max_surge: 1,
min_ready_seconds: 0,
progress_deadline_seconds: 600,
rollback_on_failure: true,
}
}
pub fn max_unavailable(mut self, max: u32) -> Self {
self.max_unavailable = max;
self
}
pub fn max_surge(mut self, max: u32) -> Self {
self.max_surge = max;
self
}
pub fn min_ready_seconds(mut self, seconds: u32) -> Self {
self.min_ready_seconds = seconds;
self
}
pub fn progress_deadline_seconds(mut self, seconds: u32) -> Self {
self.progress_deadline_seconds = seconds;
self
}
pub fn rollback_on_failure(mut self, rollback: bool) -> Self {
self.rollback_on_failure = rollback;
self
}
pub fn execute(&self) -> Result<DeploymentStatus, Error> {
println!(
"Executing Rolling deployment for {}/{}: {} -> {} (max_unavailable={}, max_surge={})",
self.service_name,
self.environment,
self.current_version,
self.next_version,
self.max_unavailable,
self.max_surge
);
Ok(DeploymentStatus {
success: true,
message: format!(
"Rolling deployment completed for {}/{}",
self.service_name, self.environment
),
current_version: self.next_version.clone(),
previous_version: Some(self.current_version.clone()),
traffic_split: None,
})
}
}
#[derive(Debug, Clone)]
pub enum DeploymentStrategy {
BlueGreen(BlueGreenDeployment),
Canary(CanaryDeployment),
Rolling(RollingDeployment),
}
impl DeploymentStrategy {
pub fn new() -> Self {
panic!("Use DeploymentManager::new() instead");
}
pub fn execute(&self) -> Result<DeploymentStatus, Error> {
match self {
DeploymentStrategy::BlueGreen(deployment) => deployment.execute(),
DeploymentStrategy::Canary(deployment) => deployment.execute(),
DeploymentStrategy::Rolling(deployment) => deployment.execute(),
}
}
}
pub struct DeploymentManager {
strategies: Vec<DeploymentStrategy>,
}
impl DeploymentManager {
pub fn new() -> Self {
Self {
strategies: Vec::new(),
}
}
pub fn add_strategy(&mut self, strategy: DeploymentStrategy) {
self.strategies.push(strategy);
}
pub fn get_strategies(&self) -> &[DeploymentStrategy] {
&self.strategies
}
pub fn clear(&mut self) {
self.strategies.clear();
}
}
impl Default for DeploymentManager {
fn default() -> Self {
Self::new()
}
}
pub struct DeploymentStrategySelector {
manager: DeploymentManager,
}
impl DeploymentStrategySelector {
pub fn new() -> Self {
Self {
manager: DeploymentManager::new(),
}
}
pub fn select_strategy(
&mut self,
config: &DeploymentConfig,
) -> Result<DeploymentStrategy, Error> {
match config.strategy.to_lowercase().as_str() {
"blue-green" | "blue_green" => {
let strategy: _ = BlueGreenDeployment::new(
config.service_name.clone(),
config.environment.clone(),
config.current_version.clone(),
config.next_version.clone(),
);
Ok(DeploymentStrategy::BlueGreen(strategy))
}
"canary" => {
let traffic_split: _ = config
.parameters
.get("traffic_split")
.and_then(|s| s.parse().ok())
.unwrap_or(10);
let mut strategy = CanaryDeployment::new(
config.service_name.clone(),
config.environment.clone(),
config.current_version.clone(),
config.next_version.clone(),
traffic_split,
);
if let Some(threshold) = config.parameters.get("promotion_threshold") {
if let Ok(t) = threshold.parse() {
strategy = strategy.promotion_threshold(t);
}
}
if let Some(interval) = config.parameters.get("health_check_interval") {
if let Ok(i) = interval.parse() {
strategy = strategy.health_check_interval(i);
}
}
Ok(DeploymentStrategy::Canary(strategy))
}
"rolling" => {
let mut strategy = RollingDeployment::new(
config.service_name.clone(),
config.environment.clone(),
config.current_version.clone(),
config.next_version.clone(),
);
if let Some(max_unavailable) = config.parameters.get("max_unavailable") {
if let Ok(m) = max_unavailable.parse() {
strategy = strategy.max_unavailable(m);
}
}
if let Some(max_surge) = config.parameters.get("max_surge") {
if let Ok(m) = max_surge.parse() {
strategy = strategy.max_surge(m);
}
}
if let Some(min_ready) = config.parameters.get("min_ready_seconds") {
if let Ok(m) = min_ready.parse() {
strategy = strategy.min_ready_seconds(m);
}
}
Ok(DeploymentStrategy::Rolling(strategy))
}
_ => Err(Error::InvalidStrategy {
strategy: config.strategy.clone(),
}),
}
}
pub fn execute_deployment(
&mut self,
config: &DeploymentConfig,
) -> Result<DeploymentStatus, Error> {
let strategy: _ = self.select_strategy(config)?;
let result: _ = strategy.execute();
if result.is_ok() {
self.manager.add_strategy(strategy);
}
result
}
}
impl Default for DeploymentStrategySelector {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Invalid deployment strategy: {strategy}")]
InvalidStrategy { strategy: String },
#[error("Deployment failed: {0}")]
DeploymentFailed(String),
#[error("Rollback failed: {0}")]
RollbackFailed(String),
#[error("Configuration error: {0}")]
ConfigurationError(String),
#[error("Deployment error: {0}")]
DeploymentError(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blue_green_deployment() {
let deployment: _ = BlueGreenDeployment::new(
"amberjs-service".to_string(),
"production".to_string(),
"v1.0.0".to_string(),
"v1.1.0".to_string(),
);
let result: _ = deployment.execute();
assert!(result.is_ok());
if let Ok(status) = result {
assert!(status.success);
assert_eq!(status.current_version, "v1.1.0");
}
}
#[test]
fn test_canary_deployment() {
let deployment: _ = CanaryDeployment::new(
"amberjs-service".to_string(),
"production".to_string(),
"v1.0.0".to_string(),
"v1.1.0".to_string(),
10,
);
let result: _ = deployment.execute();
assert!(result.is_ok());
if let Ok(status) = result {
assert!(status.success);
assert_eq!(status.traffic_split, Some(10));
}
}
#[test]
fn test_canary_promotion() {
let deployment: _ = CanaryDeployment::new(
"amberjs-service".to_string(),
"production".to_string(),
"v1.0.0".to_string(),
"v1.1.0".to_string(),
10,
);
let result: _ = deployment.promote_canary();
assert!(result.is_ok());
if let Ok(status) = result {
assert!(status.success);
assert_eq!(status.current_version, "v1.1.0");
assert_eq!(status.traffic_split, Some(100));
}
}
#[test]
fn test_canary_rollback() {
let deployment: _ = CanaryDeployment::new(
"amberjs-service".to_string(),
"production".to_string(),
"v1.0.0".to_string(),
"v1.1.0".to_string(),
10,
);
let result: _ = deployment.rollback_canary();
assert!(result.is_ok());
if let Ok(status) = result {
assert!(status.success);
assert_eq!(status.current_version, "v1.0.0");
assert_eq!(status.traffic_split, Some(0));
}
}
#[test]
fn test_rolling_deployment() {
let deployment: _ = RollingDeployment::new(
"amberjs-service".to_string(),
"production".to_string(),
"v1.0.0".to_string(),
"v1.1.0".to_string(),
);
let result: _ = deployment.execute();
assert!(result.is_ok());
if let Ok(status) = result {
assert!(status.success);
assert_eq!(status.current_version, "v1.1.0");
}
}
#[test]
fn test_rolling_deployment_params() {
let deployment: _ = RollingDeployment::new(
"amberjs-service".to_string(),
"production".to_string(),
"v1.0.0".to_string(),
"v1.1.0".to_string(),
)
.max_unavailable(2)
.max_surge(2)
.min_ready_seconds(30);
assert_eq!(deployment.max_unavailable, 2);
assert_eq!(deployment.max_surge, 2);
assert_eq!(deployment.min_ready_seconds, 30);
}
#[test]
fn test_strategy_selection() {
let mut selector = DeploymentStrategySelector::new();
let config: _ = DeploymentConfig {
strategy: "blue-green".to_string(),
service_name: "amberjs-service".to_string(),
environment: "production".to_string(),
current_version: "v1.0.0".to_string(),
next_version: "v1.1.0".to_string(),
parameters: std::collections::HashMap::new(),
};
let result: _ = selector.select_strategy(&config);
assert!(result.is_ok());
assert!(matches!(result.unwrap(), DeploymentStrategy::BlueGreen(_)));
}
#[test]
fn test_canary_strategy_with_params() {
let mut selector = DeploymentStrategySelector::new();
let mut params = std::collections::HashMap::new();
params.insert("traffic_split".to_string(), "20".to_string());
params.insert("promotion_threshold".to_string(), "99".to_string());
let config: _ = DeploymentConfig {
strategy: "canary".to_string(),
service_name: "amberjs-service".to_string(),
environment: "production".to_string(),
current_version: "v1.0.0".to_string(),
next_version: "v1.1.0".to_string(),
parameters: params,
};
let result: _ = selector.select_strategy(&config);
assert!(result.is_ok());
if let Ok(DeploymentStrategy::Canary(canary)) = result {
assert_eq!(canary.traffic_split, 20);
assert_eq!(canary.promotion_threshold, 99);
}
}
#[test]
fn test_invalid_strategy() {
let selector: _ = DeploymentStrategySelector::new();
let config: _ = DeploymentConfig {
strategy: "invalid".to_string(),
service_name: "amberjs-service".to_string(),
environment: "production".to_string(),
current_version: "v1.0.0".to_string(),
next_version: "v1.1.0".to_string(),
parameters: std::collections::HashMap::new(),
};
let result: _ = selector.select_strategy(&config);
assert!(result.is_err());
}
#[test]
fn test_deployment_manager() {
let mut manager = DeploymentManager::new();
let strategy: _ = BlueGreenDeployment::new(
"amberjs-service".to_string(),
"production".to_string(),
"v1.0.0".to_string(),
"v1.1.0".to_string(),
);
manager.add_strategy(DeploymentStrategy::BlueGreen(strategy));
assert_eq!(manager.get_strategies().len(), 1);
}
}
use std::collections::{BTreeMap, HashMap};