huskies: merge 1118 story Automate per-project docker image builds (huskies-project-base + per-stack overlays)

This commit is contained in:
dave
2026-05-17 16:21:11 +00:00
parent 265e6f9a15
commit f8b1e14b74
3 changed files with 89 additions and 3 deletions
@@ -518,7 +518,7 @@ async fn handle_adopt_project(
Ok(out) => {
let stderr = String::from_utf8_lossy(&out.stderr);
let _ = tokio::fs::remove_dir_all(&ssh_key_dir).await;
format!("Docker container launch failed: {}", stderr.trim())
interpret_docker_run_error(&stderr, &image)
}
Err(e) => {
let _ = tokio::fs::remove_dir_all(&ssh_key_dir).await;
@@ -891,9 +891,9 @@ pub async fn handle_new_project(
let stderr = String::from_utf8_lossy(&out.stderr);
let _ = tokio::fs::remove_dir_all(&host_path).await;
let _ = tokio::fs::remove_dir_all(&ssh_key_dir).await;
let base_msg = interpret_docker_run_error(&stderr, &image);
format!(
"Docker container launch failed: {}\n\nPartial state removed at `{}`.",
stderr.trim(),
"{base_msg}\n\nPartial state removed at `{}`.",
host_path.display()
)
}
@@ -908,6 +908,22 @@ pub async fn handle_new_project(
}
}
/// Convert a failed `docker run` stderr into an actionable chat message.
///
/// When Docker cannot find the image locally it prints `Unable to find image`.
/// That bare error is unhelpful — this function maps it to a message that tells
/// the user which script to run. All other failures are passed through as-is.
fn interpret_docker_run_error(stderr: &str, image: &str) -> String {
if stderr.contains("Unable to find image") {
format!(
"Image `{image}` not found locally. \
Build project images first by running `script/build-project-images`."
)
} else {
format!("Docker container launch failed: {}", stderr.trim())
}
}
/// Find a free TCP port by attempting to bind starting from `start`.
///
/// Scans up to 100 ports above `start` and returns the first available one.
@@ -1257,6 +1273,37 @@ mod tests {
assert!(warnings.is_empty());
}
// ── Missing-image error path ─────────────────────────────────────────────
#[test]
fn interpret_docker_run_error_missing_image_points_at_script() {
let stderr = "Unable to find image 'huskies-project-rust:latest' locally\n\
docker: Error response from daemon: pull access denied";
let msg = interpret_docker_run_error(stderr, "huskies-project-rust");
assert!(
msg.contains("script/build-project-images"),
"expected script name in error message, got: {msg}"
);
assert!(
msg.contains("huskies-project-rust"),
"expected image name in error message, got: {msg}"
);
}
#[test]
fn interpret_docker_run_error_other_failure_passes_through() {
let stderr = "Error response from daemon: container name already in use";
let msg = interpret_docker_run_error(stderr, "huskies-project-rust");
assert!(
msg.contains("Docker container launch failed"),
"expected generic error message, got: {msg}"
);
assert!(
msg.contains("container name already in use"),
"expected original stderr in message, got: {msg}"
);
}
#[test]
fn detect_stack_jvm_build_gradle_kts() {
let dir = tempfile::tempdir().unwrap();