use anyhow::{Context, Result};
use std::process::Command;
pub fn push() -> Result<()> {
let dockerfile = ".devcontainer/Dockerfile";
let is_kap_repo = std::fs::read_to_string("Cargo.toml")
.map(|c| c.contains("name = \"kap\""))
.unwrap_or(false);
if !is_kap_repo || !std::path::Path::new(dockerfile).exists() {
anyhow::bail!(
"This must be run from the kap source directory (where Cargo.toml defines the kap crate)."
);
}
eprintln!("[dev] building host binary...");
let status = Command::new("cargo")
.args(["install", "--path", "."])
.status()
.context("failed to run cargo install")?;
if !status.success() {
anyhow::bail!("cargo install failed");
}
eprintln!("[dev] building Linux binary...");
let status = Command::new("docker")
.args([
"build", "--target", "proxy", "-t", "kap-dev", "-f", dockerfile, ".",
])
.status()
.context("failed to run docker build")?;
if !status.success() {
anyhow::bail!("docker build failed");
}
let tmp_binary = std::env::temp_dir().join("kap-dev-linux");
let create_output = Command::new("docker")
.args(["create", "kap-dev"])
.output()
.context("failed to create temp container")?;
if !create_output.status.success() {
anyhow::bail!("docker create failed");
}
let container_id = String::from_utf8_lossy(&create_output.stdout)
.trim()
.to_string();
let cp_status = Command::new("docker")
.args([
"cp",
&format!("{container_id}:/usr/local/bin/kap"),
tmp_binary.to_str().unwrap(),
])
.status();
let _ = Command::new("docker").args(["rm", &container_id]).output();
cp_status.context("failed to extract binary from image")?;
let sidecars = find_all_sidecars()?;
if sidecars.is_empty() {
eprintln!("[dev] no running sidecar containers found");
return Ok(());
}
for sidecar in &sidecars {
eprintln!("[dev] deploying to {sidecar}...");
let status = Command::new("docker")
.args([
"cp",
tmp_binary.to_str().unwrap(),
&format!("{sidecar}:/usr/local/bin/kap"),
])
.status()
.context("docker cp failed")?;
if !status.success() {
eprintln!("[dev] warning: failed to copy binary to {sidecar}");
continue;
}
let status = Command::new("docker")
.args(["restart", sidecar])
.status()
.context("docker restart failed")?;
if !status.success() {
eprintln!("[dev] warning: failed to restart {sidecar}");
}
}
let _ = std::fs::remove_file(&tmp_binary);
eprintln!();
eprintln!("[dev] pushed to {} sidecar(s). Next steps:", sidecars.len());
eprintln!(" cd <project> && kap up bring app containers back");
eprintln!();
eprintln!(" WARNING: do NOT use --reset, it will pull the published image");
eprintln!(" and overwrite the dev binary you just pushed.");
Ok(())
}
fn find_all_sidecars() -> Result<Vec<String>> {
let output = Command::new("docker")
.args(["ps", "--format", "{{.Names}}"])
.output()
.context("failed to run docker ps")?;
let names = String::from_utf8_lossy(&output.stdout);
Ok(names
.lines()
.filter(|n| n.contains("kap-kap") || n.ends_with("-kap-1"))
.map(String::from)
.collect())
}