use anyhow::Result;
use owo_colors::OwoColorize;
use crate::api::client::{ApiClient, NewEnvironment};
use crate::api::models::{Environment, ServiceCopy};
use crate::exit;
use crate::output::Format;
use crate::render::sanitise;
use crate::render::table::{Cell, Table};
use crate::render::theme;
pub async fn list(client: &ApiClient, project_reference: &str, format: Format) -> Result<()> {
let project = client.find_project(project_reference).await?;
let answer = client.environments(&project.id).await?;
if format.answered_in_json(&answer.raw) {
return Ok(());
}
let mut table = Table::new(&["entorno", "slug", "servicios", ""]);
for environment in &answer.body.environments {
table.push(vec![
Cell::styled(environment.name.clone(), theme::strong()),
Cell::styled(environment.slug.clone(), theme::muted()),
Cell::plain(
environment
.service_count
.map_or_else(|| "—".to_owned(), |count| count.to_string()),
),
Cell::styled(
if environment.is_default {
"por defecto"
} else {
""
},
theme::muted(),
),
]);
}
table.print_or(format_args!("`{}` no tiene entornos.", project.name));
Ok(())
}
pub struct EnvironmentRequest<'a> {
pub project: &'a str,
pub name: &'a str,
pub from: Option<&'a str>,
pub empty: bool,
pub no_deploy: bool,
}
pub async fn create(
client: &ApiClient,
request: EnvironmentRequest<'_>,
format: Format,
) -> Result<()> {
let project = client.find_project(request.project).await?;
let answer = client
.create_environment(
&project.id,
&NewEnvironment {
name: request.name,
copy_from: request.from,
empty: request.empty,
deploy: !request.no_deploy,
},
)
.await?;
if format.answered_in_json(&answer.raw) {
return Ok(());
}
let created = &answer.body;
println!();
println!(
" {} {} creado en {}{}",
theme::CHECK.style(theme::success()),
sanitise::plain(&created.environment.name).style(theme::strong()),
sanitise::plain(&project.name).style(theme::strong()),
created
.copied_from
.as_ref()
.map_or_else(String::new, |source| format!(
", copia de {}",
sanitise::plain(&source.name)
)),
);
copies(&created.services);
println!(
" {}",
"Los volúmenes empiezan vacíos: los datos no se copian.".style(theme::muted())
);
println!(
" {}",
format!(
"fundaia --env {} services {}",
created.environment.slug, project.slug
)
.style(theme::muted())
);
println!();
Ok(())
}
pub async fn rename(
client: &ApiClient,
project_reference: &str,
environment: &str,
name: &str,
format: Format,
) -> Result<()> {
let project = client.find_project(project_reference).await?;
let answer = client
.rename_environment(&project.id, environment, name)
.await?;
if format.answered_in_json(&answer.raw) {
return Ok(());
}
println!(
" {} {} {}",
theme::CHECK.style(theme::success()),
sanitise::plain(&answer.body.environment.name).style(theme::strong()),
format!("(sigue siendo {})", answer.body.environment.slug).style(theme::muted()),
);
Ok(())
}
pub async fn remove(
client: &ApiClient,
project_reference: &str,
environment_reference: &str,
confirmation: Option<&str>,
format: Format,
) -> Result<()> {
let project = client.find_project(project_reference).await?;
let environment = named(
client.environments(&project.id).await?.body.environments,
environment_reference,
)?;
if confirmation != Some(environment.name.as_str())
&& confirmation != Some(environment.slug.as_str())
{
return Err(exit::Refused::invalid(format!(
"eliminar el entorno `{}` borra sus servicios, sus volúmenes con lo que tengan dentro y sus copias: repite su nombre con --confirm {}",
environment.name, environment.slug,
))
.into());
}
client
.delete_environment(
&project.id,
&environment.id,
confirmation.unwrap_or_default(),
)
.await?;
if format.is_json() {
crate::output::emit(&serde_json::json!({ "deleted": environment.id }));
return Ok(());
}
println!(
" {} {} eliminado de {}",
theme::CHECK.style(theme::success()),
sanitise::plain(&environment.name).style(theme::strong()),
sanitise::plain(&project.name).style(theme::strong()),
);
Ok(())
}
pub struct CopyRequest<'a> {
pub project: &'a str,
pub service: &'a str,
pub from: Option<&'a str>,
pub to: &'a str,
pub no_deploy: bool,
}
pub async fn copy(client: &ApiClient, request: CopyRequest<'_>, format: Format) -> Result<()> {
let source = match request.from {
Some(from) => client.with_environment(Some(from)),
None => client.clone(),
};
let (project, service) = source.locate(request.project, request.service).await?;
let answer = client
.copy_service_to_environment(&project.id, request.to, &service.id, !request.no_deploy)
.await?;
if format.answered_in_json(&answer.raw) {
return Ok(());
}
println!();
copies(std::slice::from_ref(&answer.body.service));
println!();
Ok(())
}
fn copies(services: &[ServiceCopy]) {
for copy in services {
let (mark, word) = match copy.status.as_str() {
"deploying" => (theme::CHECK.style(theme::success()), "desplegando"),
"copied" => (theme::PENDING.style(theme::muted()), "copiado"),
_ => (theme::CROSS.style(theme::danger()), "no copiado"),
};
println!(
" {mark} {} {}",
sanitise::plain(©.name).style(theme::strong()),
word.style(theme::muted()),
);
if let Some(host) = ©.host {
println!(
" {} {}",
theme::ARROW.style(theme::muted()),
sanitise::plain(host)
);
}
if let Some(reason) = ©.reason {
println!(" {}", sanitise::plain(reason).style(theme::muted()));
}
for note in ©.notes {
println!(" {}", sanitise::plain(note).style(theme::muted()));
}
}
}
fn named(environments: Vec<Environment>, reference: &str) -> Result<Environment> {
let needle = reference.trim().to_lowercase();
environments
.into_iter()
.find(|one| one.id == reference || one.slug == needle || one.name.to_lowercase() == needle)
.ok_or_else(|| {
exit::Refused::not_found(format!("there is no environment called `{reference}`")).into()
})
}
#[cfg(test)]
mod tests {
use super::*;
fn staging() -> Environment {
Environment {
id: "env_1".to_owned(),
name: "Staging".to_owned(),
slug: "staging".to_owned(),
is_default: false,
service_count: Some(2),
}
}
#[test]
fn it_should_find_an_environment_by_its_name_in_any_case() {
assert_eq!(named(vec![staging()], "STAGING").unwrap().id, "env_1");
}
#[test]
fn it_should_find_an_environment_by_its_id() {
assert_eq!(named(vec![staging()], "env_1").unwrap().slug, "staging");
}
#[test]
fn it_should_refuse_an_environment_that_is_not_there() {
assert!(named(vec![staging()], "qa").is_err());
}
}