use anyhow::Result;
use base64::Engine;
use std::path::PathBuf;
const DEVG_CLI_PORT: u16 = 3130;
fn sidecar_host() -> String {
std::env::var("HTTP_PROXY")
.ok()
.and_then(|v| {
v.strip_prefix("http://")
.and_then(|rest| rest.split(':').next())
.map(String::from)
})
.unwrap_or_else(|| "172.28.0.3".to_string())
}
pub async fn run(tool: &str, args: &[String]) -> Result<()> {
let host = sidecar_host();
let url = format!("http://{host}:{DEVG_CLI_PORT}/{tool}");
let saved_proxy: Vec<(String, String)> =
["HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy"]
.iter()
.filter_map(|k| std::env::var(k).ok().map(|v| (k.to_string(), v)))
.collect();
unsafe {
std::env::remove_var("HTTP_PROXY");
std::env::remove_var("HTTPS_PROXY");
std::env::remove_var("http_proxy");
std::env::remove_var("https_proxy");
}
let cwd = std::env::current_dir()
.ok()
.and_then(|p| p.to_str().map(String::from))
.unwrap_or_default();
let client = reqwest::Client::builder().no_proxy().build()?;
let resp = client
.post(&url)
.json(&serde_json::json!({"args": args, "cwd": cwd}))
.timeout(std::time::Duration::from_secs(120))
.send()
.await?;
let mode = resp
.headers()
.get("x-mode")
.and_then(|v| v.to_str().ok())
.map(String::from);
if mode.as_deref() == Some("direct") {
return run_direct(tool, args, &resp, &saved_proxy).await;
}
let exit_code: i32 = resp
.headers()
.get("x-exit-code")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse().ok())
.unwrap_or(1);
let stderr_b64 = resp
.headers()
.get("x-stderr")
.and_then(|v| v.to_str().ok())
.map(String::from);
let stdout = resp.bytes().await?;
if let Some(b64) = stderr_b64
&& let Ok(decoded) = base64::engine::general_purpose::STANDARD.decode(&b64)
{
use std::io::Write;
let _ = std::io::stderr().write_all(&decoded);
}
{
use std::io::Write;
let _ = std::io::stdout().write_all(&stdout);
}
if exit_code != 0 {
anyhow::bail!("{tool} exited with code {exit_code}");
}
Ok(())
}
async fn run_direct(
tool: &str,
args: &[String],
resp: &reqwest::Response,
saved_proxy: &[(String, String)],
) -> Result<()> {
let env_vars = resp
.headers()
.get("x-env")
.and_then(|v| v.to_str().ok())
.and_then(|b64| base64::engine::general_purpose::STANDARD.decode(b64).ok())
.and_then(|bytes| String::from_utf8(bytes).ok())
.map(|s| {
s.lines()
.filter_map(|line| {
let (k, v) = line.split_once('=')?;
Some((k.to_string(), v.to_string()))
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
let real_binary = find_real_binary(tool)?;
use std::os::unix::process::CommandExt;
let mut cmd = std::process::Command::new(&real_binary);
cmd.args(args);
for (k, v) in saved_proxy {
cmd.env(k, v);
}
for (k, v) in &env_vars {
cmd.env(k, v);
}
let err = cmd.exec();
anyhow::bail!("exec {}: {err}", real_binary.display());
}
fn find_real_binary(name: &str) -> Result<PathBuf> {
let path = std::env::var("PATH").unwrap_or_default();
find_binary_in_path(name, &path)
}
fn find_binary_in_path(name: &str, path: &str) -> Result<PathBuf> {
for dir in path.split(':') {
if dir.starts_with("/opt/kap") {
continue;
}
let candidate = PathBuf::from(dir).join(name);
if candidate.is_file() && !is_kap_shim(&candidate) {
return Ok(candidate);
}
}
anyhow::bail!("{name}: not found in PATH (install it in your app container for direct mode)")
}
fn is_kap_shim(path: &PathBuf) -> bool {
std::fs::read_to_string(path)
.map(|content| content.contains("sidecar-cli-shim"))
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
fn tempdir(suffix: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("kap-shim-{}-{suffix}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
dir
}
#[test]
fn find_binary_skips_opt_kap() {
let real_dir = tempdir("real-bin");
let real_path = real_dir.join("mytool");
fs::write(&real_path, "#!/bin/sh\necho real").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&real_path, fs::Permissions::from_mode(0o755)).unwrap();
}
let path = format!("/opt/kap/bin:{}", real_dir.display());
let found = find_binary_in_path("mytool", &path).unwrap();
assert_eq!(found, real_path);
fs::remove_dir_all(&real_dir).unwrap();
}
#[test]
fn find_binary_not_found() {
let result = find_binary_in_path("surely_not_a_real_binary", "/nonexistent");
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("not found in PATH")
);
}
}