153 lines
4.1 KiB
Rust
153 lines
4.1 KiB
Rust
use std::time::{Duration, Instant};
|
|
|
|
use anyhow::Result;
|
|
use colored::Colorize;
|
|
|
|
use crate::config::Project;
|
|
use crate::{local, ssh, sync};
|
|
|
|
pub fn deploy(name: &str, project: &Project, dry_run: bool) -> Result<()> {
|
|
println!("deploying '{name}'");
|
|
|
|
let local_cmds = project
|
|
.local
|
|
.as_ref()
|
|
.map_or([].as_slice(), |c| c.commands.as_slice());
|
|
let remote_cmds = project
|
|
.remote
|
|
.as_ref()
|
|
.map_or([].as_slice(), |c| c.commands.as_slice());
|
|
|
|
if dry_run {
|
|
print_plan(project, local_cmds, remote_cmds);
|
|
return Ok(());
|
|
}
|
|
|
|
if local_cmds.is_empty() && project.sync.is_empty() && remote_cmds.is_empty() {
|
|
println!("nothing to do: no local commands, sync rules or remote commands");
|
|
return Ok(());
|
|
}
|
|
|
|
let total = Instant::now();
|
|
|
|
for cmd in local_cmds {
|
|
let step = Instant::now();
|
|
if let Err(err) = local::execute(cmd, &project.workdir) {
|
|
fail_line("🟢 [Local]", &format!("running: {cmd}..."), err.to_string());
|
|
return Err(err);
|
|
}
|
|
success_line(
|
|
"🟢 [Local]",
|
|
&format!("running: {cmd}..."),
|
|
"Success!",
|
|
step.elapsed(),
|
|
);
|
|
}
|
|
|
|
if !project.sync.is_empty() || !remote_cmds.is_empty() {
|
|
let mut session = connect(project)?;
|
|
|
|
for rule in &project.sync {
|
|
let step = Instant::now();
|
|
let target = rule.target.to_string_lossy().to_string();
|
|
let result = sync::upload(&mut session, &rule.source, &target);
|
|
let label = format!("transferring {} to {}...", rule.source.display(), target);
|
|
match result {
|
|
Ok(stats) => success_line(
|
|
"🔵 [SFTP]",
|
|
&label,
|
|
&format!("Done! ({stats})"),
|
|
step.elapsed(),
|
|
),
|
|
Err(err) => {
|
|
fail_line("🔵 [SFTP]", &label, err.to_string());
|
|
return Err(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
for cmd in remote_cmds {
|
|
let step = Instant::now();
|
|
if let Err(err) = session.exec(cmd) {
|
|
fail_line(
|
|
"🟡 [Remote]",
|
|
&format!("running: {cmd}..."),
|
|
err.to_string(),
|
|
);
|
|
return Err(err);
|
|
}
|
|
success_line(
|
|
"🟡 [Remote]",
|
|
&format!("running: {cmd}..."),
|
|
"Success!",
|
|
step.elapsed(),
|
|
);
|
|
}
|
|
}
|
|
|
|
println!(
|
|
"🎉 {} (total {})",
|
|
"Deploy successful!".green().bold(),
|
|
fmt_dur(total.elapsed())
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
fn print_plan(project: &Project, local_cmds: &[String], remote_cmds: &[String]) {
|
|
println!("dry-run plan:");
|
|
for cmd in local_cmds {
|
|
println!(" 🟢 [Local] {cmd}");
|
|
}
|
|
for rule in &project.sync {
|
|
println!(
|
|
" 🔵 [SFTP] {} -> {}",
|
|
rule.source.display(),
|
|
rule.target.display()
|
|
);
|
|
}
|
|
for cmd in remote_cmds {
|
|
println!(" 🟡 [Remote] {cmd}");
|
|
}
|
|
println!("{}", "nothing executed".dimmed());
|
|
}
|
|
|
|
fn connect(project: &Project) -> Result<ssh::Session> {
|
|
let target = ssh::Target {
|
|
host_alias: project.host.clone(),
|
|
user: project.user.clone(),
|
|
port: project.port,
|
|
key_path: project.key_path.clone(),
|
|
};
|
|
let resolved = ssh::resolve(&target)?;
|
|
ssh::connect(&resolved)
|
|
}
|
|
|
|
fn success_line(tag: &str, action: &str, status: &str, took: Duration) {
|
|
println!(
|
|
"{} {} {} ({})",
|
|
tag.green().bold(),
|
|
action,
|
|
status.green(),
|
|
fmt_dur(took).dimmed()
|
|
);
|
|
}
|
|
|
|
fn fail_line(tag: &str, action: &str, message: String) {
|
|
println!(
|
|
"{} {} {}",
|
|
tag.green().bold(),
|
|
action,
|
|
"FAILED".red().bold()
|
|
);
|
|
eprintln!("{}", message.red());
|
|
}
|
|
|
|
fn fmt_dur(d: Duration) -> String {
|
|
let ms = d.as_millis();
|
|
if ms < 1000 {
|
|
format!("{ms}ms")
|
|
} else {
|
|
format!("{:.2}s", d.as_secs_f64())
|
|
}
|
|
}
|