use std::path::Path;
use anyhow::{anyhow, Context};
use futures::future::join_all;
use monitor_client::api::execute;
use serde::Deserialize;
use strum::Display;
use crate::monitor_client;
pub async fn run_execution(path: &Path) -> anyhow::Result<()> {
let ExecutionFile { name, stages } = crate::parse_toml_file(path)?;
info!("EXECUTION: {name}");
info!("path: {path:?}");
println!("{stages:#?}");
crate::wait_for_enter("EXECUTE")?;
run_stages(stages)
.await
.context("failed during a stage. terminating run.")?;
info!("finished successfully ✅");
Ok(())
}
#[derive(Debug, Clone, Deserialize)]
pub struct ExecutionFile {
pub name: String,
#[serde(rename = "stage")]
pub stages: Vec<Stage>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Stage {
pub name: String,
pub action: ExecutionType,
pub targets: Vec<String>,
}
#[derive(Debug, Clone, Copy, Deserialize, Display)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum ExecutionType {
Build,
Deploy,
StartContainer,
StopContainer,
DestroyContainer,
}
pub async fn run_stages(stages: Vec<Stage>) -> anyhow::Result<()> {
for Stage {
name,
action,
targets,
} in stages
{
info!("running {action} stage: {name}... ⏳");
match action {
ExecutionType::Build => {
trigger_builds_in_parallel(&targets).await?;
}
ExecutionType::Deploy => {
redeploy_deployments_in_parallel(&targets).await?;
}
ExecutionType::StartContainer => {
start_containers_in_parallel(&targets).await?
}
ExecutionType::StopContainer => {
stop_containers_in_parallel(&targets).await?
}
ExecutionType::DestroyContainer => {
destroy_containers_in_parallel(&targets).await?;
}
}
info!("finished {action} stage: {name} ✅");
}
Ok(())
}
async fn redeploy_deployments_in_parallel(
deployments: &[String],
) -> anyhow::Result<()> {
let futes = deployments.iter().map(|deployment| async move {
monitor_client()
.execute(execute::Deploy { deployment: deployment.to_string(), stop_signal: None, stop_time: None })
.await
.with_context(|| format!("failed to deploy {deployment}"))
.and_then(|update| {
if update.success {
Ok(())
} else {
Err(anyhow!(
"failed to deploy {deployment}. operation unsuccessful, see monitor update"
))
}
})
});
join_all(futes).await.into_iter().collect()
}
async fn start_containers_in_parallel(
deployments: &[String],
) -> anyhow::Result<()> {
let futes = deployments.iter().map(|deployment| async move {
monitor_client()
.execute(execute::StartContainer { deployment: deployment.to_string() })
.await
.with_context(|| format!("failed to start container {deployment}"))
.and_then(|update| {
if update.success {
Ok(())
} else {
Err(anyhow!(
"failed to start container {deployment}. operation unsuccessful, see monitor update"
))
}
})
});
join_all(futes).await.into_iter().collect()
}
async fn stop_containers_in_parallel(
deployments: &[String],
) -> anyhow::Result<()> {
let futes = deployments.iter().map(|deployment| async move {
monitor_client()
.execute(execute::StopContainer { deployment: deployment.to_string(), signal: None, time: None })
.await
.with_context(|| format!("failed to stop container {deployment}"))
.and_then(|update| {
if update.success {
Ok(())
} else {
Err(anyhow!(
"failed to stop container {deployment}. operation unsuccessful, see monitor update"
))
}
})
});
join_all(futes).await.into_iter().collect()
}
async fn destroy_containers_in_parallel(
deployments: &[String],
) -> anyhow::Result<()> {
let futes = deployments.iter().map(|deployment| async move {
monitor_client()
.execute(execute::RemoveContainer { deployment: deployment.to_string(), signal: None, time: None })
.await
.with_context(|| format!("failed to destroy container {deployment}"))
.and_then(|update| {
if update.success {
Ok(())
} else {
Err(anyhow!(
"failed to destroy container {deployment}. operation unsuccessful, see monitor update"
))
}
})
});
join_all(futes).await.into_iter().collect()
}
async fn trigger_builds_in_parallel(
builds: &[String],
) -> anyhow::Result<()> {
let futes = builds.iter().map(|build| async move {
monitor_client()
.execute(execute::RunBuild { build: build.to_string() })
.await
.with_context(|| format!("failed to build {build}"))
.and_then(|update| {
if update.success {
Ok(())
} else {
Err(anyhow!(
"failed to build {build}. operation unsuccessful, see monitor update"
))
}
})
});
join_all(futes).await.into_iter().collect()
}