@@ -43,29 +43,36 @@
timeout: u64,
log_reporter: &LogReporter,
) -> Result<ExecResult, Box<dyn std::error::Error + Send + Sync>> {
// Pull image first to ensure it's available
eprintln!("Pulling image: {image}");
log_reporter
.append(&format!("Pulling image: {image}"))
.await;
let pull_output = std::process::Command::new("docker")
.args(["pull", image])
.output();
match pull_output {
Ok(output) if output.status.success() => {
eprintln!("Image ready: {image}");
}
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
let msg = format!("Warning: docker pull failed: {stderr}");
eprintln!("{msg}");
log_reporter.append(&msg).await;
}
Err(e) => {
let msg = format!("Warning: docker pull error: {e}");
eprintln!("{msg}");
log_reporter.append(&msg).await;
// Pull image first to ensure it's available (skip for locally-built prepared images)
if !image.starts_with("anvil-prepared:") {
// Login to Anvil registry if credentials are available and image is from that registry
ensure_registry_login(image, env, log_reporter).await;
eprintln!("Pulling image: {image}");
log_reporter
.append(&format!("Pulling image: {image}"))
.await;
let pull_output = std::process::Command::new("docker")
.args(["pull", image])
.output();
match pull_output {
Ok(output) if output.status.success() => {
eprintln!("Image ready: {image}");
}
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
let msg = format!("Warning: docker pull failed: {stderr}");
eprintln!("{msg}");
log_reporter.append(&msg).await;
}
Err(e) => {
let msg = format!("Warning: docker pull error: {e}");
eprintln!("{msg}");
log_reporter.append(&msg).await;
}
}
} else {
eprintln!("Using local prepared image: {image}");
}
let workspace_str = workspace.to_str().unwrap_or(".");
@@ -157,6 +164,70 @@
log_reporter,
)
.await
}
/// If ANVIL_REGISTRY credentials are present in the env and the image is from
/// that registry, run `docker login` with `--password-stdin` before pulling.
/// Non-fatal: if login fails the pull may still succeed for public images.
async fn ensure_registry_login(
image: &str,
env: &HashMap<String, String>,
log_reporter: &LogReporter,
) {
let registry = match env.get("ANVIL_REGISTRY") {
Some(r) if !r.is_empty() => r,
_ => return,
};
let token = match env.get("ANVIL_REGISTRY_TOKEN") {
Some(t) if !t.is_empty() => t,
_ => return,
};
let user = env
.get("ANVIL_REGISTRY_USER")
.map(|s| s.as_str())
.unwrap_or("anvil");
// Only login if the image actually comes from this registry
if !image.starts_with(registry.as_str()) {
return;
}
eprintln!("Logging in to registry: {registry}");
log_reporter
.append(&format!("Logging in to registry: {registry}"))
.await;
// Use --password-stdin for security (avoids token in process args)
let result = std::process::Command::new("docker")
.args(["login", registry, "-u", user, "--password-stdin"])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.and_then(|mut child| {
use std::io::Write;
if let Some(ref mut stdin) = child.stdin {
stdin.write_all(token.as_bytes())?;
}
child.wait_with_output()
});
match result {
Ok(output) if output.status.success() => {
eprintln!("Registry login succeeded");
}
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
let msg = format!("Warning: registry login failed: {stderr}");
eprintln!("{msg}");
log_reporter.append(&msg).await;
}
Err(e) => {
let msg = format!("Warning: registry login error: {e}");
eprintln!("{msg}");
log_reporter.append(&msg).await;
}
}
}
async fn run_and_stream(