use std::time::Duration;
use anyhow::{bail, Result};
use owo_colors::OwoColorize;
use crate::api::client::ApiClient;
use crate::api::models::Deployment;
use crate::render::logs::{render_line, LineFormat};
use crate::render::pipeline::{epoch_millis, format_millis, PipelineView};
use crate::render::theme;
pub async fn run(
client: &ApiClient,
project_reference: &str,
service_reference: &str,
restart: bool,
follow: bool,
) -> Result<()> {
let project = client.find_project(project_reference).await?;
let service = client.find_service(&project.id, service_reference).await?;
let deployment = if restart {
client.restart(&service.id).await?
} else {
client.deploy(&service.id).await?
};
println!();
println!(
" {} {} {} despliegue #{}",
theme::ARROW.style(theme::accent()),
project.name.style(theme::muted()),
service.name.style(theme::strong()),
deployment.number.style(theme::strong()),
);
println!();
if !follow {
println!(
" {}",
format!(
"Sigue el progreso con: fundaia logs {} {}",
project.slug, service.slug
)
.style(theme::muted()),
);
return Ok(());
}
watch(client, &deployment).await
}
pub async fn rollback(
client: &ApiClient,
project_reference: &str,
service_reference: &str,
number: u32,
) -> Result<()> {
let project = client.find_project(project_reference).await?;
let service = client.find_service(&project.id, service_reference).await?;
let deployments = client.deployments(&service.id).await?;
let Some(target) = deployments
.iter()
.find(|deployment| deployment.number == number)
else {
bail!("`{}` has no deployment #{number}", service.name);
};
let started = client.rollback(&target.id).await?;
println!();
println!(
" {} Volviendo {} a #{}",
theme::ARROW.style(theme::accent()),
service.name.style(theme::strong()),
number.style(theme::strong()),
);
println!();
watch(client, &started).await
}
pub async fn watch(client: &ApiClient, deployment: &Deployment) -> Result<()> {
let interactive = std::io::IsTerminal::is_terminal(&std::io::stdout());
let mut view = PipelineView::new(interactive);
let logs = tokio::spawn({
let client = client.clone();
let deployment_id = deployment.id.clone();
async move { stream_logs(&client, &deployment_id).await }
});
let outcome = poll_pipeline(client, &deployment.id, &mut view).await;
logs.abort();
outcome
}
async fn poll_pipeline(
client: &ApiClient,
deployment_id: &str,
view: &mut PipelineView,
) -> Result<()> {
loop {
let detail = client.deployment(deployment_id).await?;
if detail.pipeline.finished {
view.finish(&detail.pipeline);
return report(&detail.deployment, detail.pipeline.succeeded);
}
view.draw(&detail.pipeline);
tokio::time::sleep(Duration::from_secs(1)).await;
}
}
fn report(deployment: &Deployment, succeeded: bool) -> Result<()> {
let elapsed = deployment
.finished_at
.as_ref()
.and_then(|finished| Some(epoch_millis(finished)? - epoch_millis(&deployment.created_at)?))
.map(format_millis)
.unwrap_or_else(|| "—".to_owned());
println!();
if succeeded {
println!(
" {} Despliegue #{} en marcha en {}",
theme::CHECK.style(theme::success()),
deployment.number,
elapsed.style(theme::strong()),
);
println!();
return Ok(());
}
println!(
" {} Despliegue #{} ha fallado tras {}",
theme::CROSS.style(theme::danger()),
deployment.number,
elapsed,
);
println!();
bail!(deployment
.failure_message
.clone()
.unwrap_or_else(|| "the deployment failed".to_owned()))
}
async fn stream_logs(client: &ApiClient, deployment_id: &str) -> Result<()> {
client
.stream_log(deployment_id, 0, |line| {
println!(" {}", render_line(&line, LineFormat::plain()));
})
.await
}