use anyhow::Result;
use owo_colors::OwoColorize;
use crate::api::client::{ApiClient, ComposeDraft};
use crate::api::models::{ComposeImport, PlacedStack, StacksPage};
use crate::commands::environments;
use crate::output::Format;
use crate::render::sanitise;
use crate::render::theme;
pub fn is_compose(path: &str, text: &str) -> bool {
let lower = path.to_lowercase();
if lower.ends_with(".yml") || lower.ends_with(".yaml") {
return true;
}
if lower.ends_with(".json") {
return false;
}
!text.trim_start().starts_with('{')
}
pub struct ComposeRequest<'a> {
pub text: &'a str,
pub name: Option<&'a str>,
pub workspace: Option<&'a str>,
pub project: Option<&'a str>,
pub repository: Option<&'a str>,
pub branch: Option<&'a str>,
pub deploy: bool,
}
pub async fn import_compose(
client: &ApiClient,
request: ComposeRequest<'_>,
format: Format,
) -> Result<()> {
let workspace = match request.workspace {
Some(reference) => Some(client.find_workspace(Some(reference)).await?.id),
None => None,
};
let project = match request.project {
Some(reference) => Some(client.find_project(reference).await?.id),
None => None,
};
let answer = client
.import_compose(&ComposeDraft {
compose: request.text,
name: request.name,
workspace_id: workspace.as_deref(),
project_id: project.as_deref(),
repository: request.repository,
branch: request.branch,
deploy: request.deploy,
})
.await?;
if format.answered_in_json(&answer.raw) {
return Ok(());
}
print_compose(&answer.body);
Ok(())
}
fn print_compose(result: &ComposeImport) {
println!();
println!(
" {} {} {} {}",
theme::CHECK.style(theme::success()),
sanitise::plain(&result.project.name).style(theme::strong()),
if result.created_project {
"creado desde el docker-compose"
} else {
"recibe los servicios del docker-compose"
},
format!("({})", sanitise::plain(&result.environment.name)).style(theme::muted()),
);
for service in &result.services {
let (mark, word) = match service.status.as_str() {
"deploying" => (
theme::CHECK.style(theme::success()).to_string(),
"desplegando",
),
"created" => (theme::CHECK.style(theme::success()).to_string(), "creado"),
_ => (theme::CROSS.style(theme::danger()).to_string(), "omitido"),
};
println!(
" {mark} {} {}",
sanitise::plain(&service.name).style(theme::strong()),
word.style(theme::muted())
);
if let Some(host) = &service.host {
println!(
" {} https://{}",
theme::ARROW.style(theme::muted()),
sanitise::plain(host)
);
}
if let Some(reason) = &service.reason {
println!(" {}", sanitise::plain(reason).style(theme::muted()));
}
for note in &service.notes {
println!(" {}", sanitise::plain(note).style(theme::muted()));
}
}
if !result.pending_variables.is_empty() {
println!();
println!(" Variables que el docker-compose no resuelve:");
for pending in &result.pending_variables {
println!(
" {}",
format!(
"fundaia vars set {} {} {} …",
result.project.slug,
sanitise::plain(&pending.service),
sanitise::plain(&pending.key)
)
.style(theme::muted())
);
}
}
println!();
}
pub async fn list_stacks(client: &ApiClient, format: Format) -> Result<()> {
let answer = client.stacks().await?;
if format.answered_in_json(&answer.raw) {
return Ok(());
}
print_stacks(&answer.body);
Ok(())
}
fn print_stacks(page: &StacksPage) {
for stack in &page.stacks {
println!();
println!(
" {} {} {}",
stack.key.style(theme::accent()),
sanitise::plain(&stack.display_name).style(theme::strong()),
format!("{} MB", stack.memory_mb).style(theme::muted()),
);
println!(
" {}",
sanitise::plain(&stack.summary).style(theme::muted())
);
let members: Vec<String> = stack
.services
.iter()
.map(|member| {
let name = sanitise::plain(&member.name);
if member.public {
format!("{name} (público)")
} else {
name
}
})
.collect();
println!(" {}", members.join(" · "));
}
println!();
}
pub struct StackRequest<'a> {
pub key: &'a str,
pub project: Option<&'a str>,
pub name: Option<&'a str>,
pub workspace: Option<&'a str>,
pub deploy: bool,
}
pub async fn add_stack(
client: &ApiClient,
request: StackRequest<'_>,
format: Format,
) -> Result<()> {
let answer = match request.project {
Some(reference) => {
let project = client.find_project(reference).await?;
client
.add_stack(&project.id, request.key, request.deploy)
.await?
}
None => {
let workspace = match request.workspace {
Some(reference) => Some(client.find_workspace(Some(reference)).await?.id),
None => None,
};
client
.create_from_stack(
request.key,
request.name,
workspace.as_deref(),
request.deploy,
)
.await?
}
};
if format.answered_in_json(&answer.raw) {
return Ok(());
}
print_stack(&answer.body);
Ok(())
}
fn print_stack(placed: &PlacedStack) {
println!();
println!(
" {} {} en {} {}",
theme::CHECK.style(theme::success()),
sanitise::plain(&placed.stack.display_name).style(theme::strong()),
sanitise::plain(&placed.project.name).style(theme::strong()),
format!("({})", sanitise::plain(&placed.environment.name)).style(theme::muted()),
);
for service in &placed.services {
let word = if service.status == "deploying" {
"desplegando"
} else {
"creado"
};
println!(
" {} {} {}",
theme::CHECK.style(theme::success()),
sanitise::plain(&service.name),
word.style(theme::muted())
);
if let Some(reason) = &service.reason {
println!(" {}", sanitise::plain(reason).style(theme::muted()));
}
for note in &service.notes {
println!(" {}", sanitise::plain(note).style(theme::muted()));
}
}
if let Some(host) = &placed.host {
println!(
" {} https://{}",
theme::ARROW.style(theme::muted()),
sanitise::plain(host)
);
}
if !placed.stack.sign_in.is_empty() {
println!(
" {}",
sanitise::plain(&placed.stack.sign_in).style(theme::muted())
);
}
println!();
}
pub async fn duplicate(
client: &ApiClient,
project_reference: &str,
service_reference: &str,
name: &str,
deploy: bool,
format: Format,
) -> Result<()> {
let (_, service) = client.locate(project_reference, service_reference).await?;
let answer = client.duplicate_service(&service.id, name, deploy).await?;
if format.answered_in_json(&answer.raw) {
return Ok(());
}
println!();
environments::copies(std::slice::from_ref(&answer.body.service));
println!();
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_should_read_a_yaml_file_as_a_compose_file() {
assert!(is_compose("docker-compose.yml", "{}"));
}
#[test]
fn it_should_read_a_json_file_as_a_manifest() {
assert!(!is_compose("tienda.fundaia.json", "services:"));
}
#[test]
fn it_should_read_a_compose_file_of_another_name_by_its_content() {
assert!(is_compose(
"compose",
"services:\n web:\n image: nginx\n"
));
}
#[test]
fn it_should_read_a_manifest_of_another_name_by_its_content() {
assert!(!is_compose("tienda", " {\"manifest\": {}}"));
}
#[test]
fn it_should_read_the_extension_whatever_its_case() {
assert!(is_compose("COMPOSE.YAML", "{}"));
}
}