use crate::github::GitHubSource;
use crate::package_manifest::PackageManifest;
use comfy_table::{presets::NOTHING, Table, TableComponent};
pub async fn run(source: &str, env: Option<&str>) -> eyre::Result<()> {
let gh = GitHubSource::parse(source)?;
println!("Installing {}...", gh.display());
let temp = tempfile::tempdir()?;
crate::github::download(&gh, temp.path()).await?;
let manifest = PackageManifest::load(temp.path())?;
manifest.validate_paths(temp.path())?;
let packages_dir = std::env::current_dir()?.join("packages");
let dest = packages_dir.join(&manifest.package.name);
if dest.exists() {
print!(
"Package '{}' already installed. Overwrite? [y/N] ",
manifest.package.name
);
use std::io::Write;
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
if !input.trim().eq_ignore_ascii_case("y") {
println!("Aborted.");
return Ok(());
}
std::fs::remove_dir_all(&dest)?;
}
std::fs::create_dir_all(&packages_dir)?;
let output = std::process::Command::new("cp")
.args([
"-r",
&temp.path().to_string_lossy(),
&dest.to_string_lossy(),
])
.output()?;
if !output.status.success() {
eyre::bail!("Failed to copy package to {}", dest.display());
}
println!();
manifest.print_summary();
println!("Installed to packages/{}/", manifest.package.name);
println!();
print!("Deploy all components? [y/N] ");
use std::io::Write;
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
if input.trim().eq_ignore_ascii_case("y") {
deploy_package(&manifest, &dest, env).await?;
} else {
println!("Skipped deploy. Deploy later from each component directory:");
for s in &manifest.services {
println!(
" cd packages/{}/{} && cufflink deploy",
manifest.package.name, s.path
);
}
for f in &manifest.frontends {
println!(
" cd packages/{}/{} && cufflink deploy",
manifest.package.name, f.path
);
}
}
Ok(())
}
async fn deploy_package(
manifest: &PackageManifest,
package_dir: &std::path::Path,
env: Option<&str>,
) -> eyre::Result<()> {
if !manifest.config.required.is_empty() {
println!("Note: Set these config keys after deployment:");
for key in &manifest.config.required {
println!(" cufflink config set {} <value>", key);
}
println!();
}
let original_dir = std::env::current_dir()?;
let mut results: Vec<(&str, &str, Result<(), String>)> = Vec::new();
for service in &manifest.services {
let service_dir = package_dir.join(&service.path);
println!("Deploying service '{}'...", service.name);
std::env::set_current_dir(&service_dir)?;
match super::deploy::run(false, None, env).await {
Ok(_) => results.push((&service.name, "service", Ok(()))),
Err(e) => {
let msg = format!("{}", e);
eprintln!(" Failed: {}", msg);
results.push((&service.name, "service", Err(msg)));
}
}
}
for frontend in &manifest.frontends {
let frontend_dir = package_dir.join(&frontend.path);
println!("Deploying frontend '{}'...", frontend.name);
std::env::set_current_dir(&frontend_dir)?;
match super::deploy::run(false, None, env).await {
Ok(_) => results.push((&frontend.name, "frontend", Ok(()))),
Err(e) => {
let msg = format!("{}", e);
eprintln!(" Failed: {}", msg);
results.push((&frontend.name, "frontend", Err(msg)));
}
}
}
std::env::set_current_dir(&original_dir)?;
println!();
let mut table = Table::new();
table.load_preset(NOTHING);
table.set_style(TableComponent::HeaderLines, '-');
table.set_style(TableComponent::MiddleHeaderIntersections, ' ');
table.set_header(vec!["COMPONENT", "TYPE", "STATUS"]);
let mut failures = 0;
for (name, kind, result) in &results {
let status = match result {
Ok(_) => "deployed",
Err(_) => {
failures += 1;
"FAILED"
}
};
table.add_row(vec![*name, *kind, status]);
}
println!("{table}");
if failures > 0 {
eyre::bail!("{} component(s) failed to deploy", failures);
}
Ok(())
}