use crate::error::{FoxhuntError, Result}; use crate::utils; use indicatif::{ProgressBar, ProgressStyle}; use std::io::{BufRead, BufReader}; use std::process::{Command, Stdio}; use std::time::Duration; /// Push a Docker image to a registry pub(crate) fn push_image(tag: &str) -> Result<()> { // Verify image exists locally if !super::image_exists(tag)? { return Err(FoxhuntError::Docker(format!( "Image '{}' does not exist locally. Build it first.", tag ))); } utils::info(&format!("Pushing Docker image: {}", tag)); // Create progress bar let pb = ProgressBar::new_spinner(); pb.set_style( ProgressStyle::default_spinner() .template("{spinner:.blue} {msg}") .unwrap() .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]), ); pb.enable_steady_tick(Duration::from_millis(100)); pb.set_message("Pushing to registry..."); // Build docker push command let mut cmd = Command::new("docker"); cmd.arg("push"); cmd.arg(tag); // Configure to capture output cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::piped()); // Spawn the process let mut child = cmd .spawn() .map_err(|e| FoxhuntError::Docker(format!("Failed to spawn docker push: {}", e)))?; // Stream stdout to show progress if let Some(stdout) = child.stdout.take() { let reader = BufReader::new(stdout); for line in reader.lines() { if let Ok(line) = line { // Update progress bar with push progress if line.contains("Pushing") || line.contains("Pushed") || line.contains("Waiting") { pb.set_message(line.clone()); } tracing::debug!("docker push: {}", line); } } } // Wait for completion let status = child .wait() .map_err(|e| FoxhuntError::Docker(format!("Failed to wait for docker push: {}", e)))?; pb.finish_and_clear(); if !status.success() { // Try to get stderr for error details let stderr = if let Some(stderr) = child.stderr { let reader = BufReader::new(stderr); let mut error_msg = String::new(); for line in reader.lines().flatten() { error_msg.push_str(&line); error_msg.push('\n'); } error_msg } else { "Unknown error".to_string() }; // Check for authentication errors if stderr.contains("authentication") || stderr.contains("unauthorized") { return Err(FoxhuntError::Docker(format!( "Docker registry authentication failed. Please run 'docker login' first.\n{}", stderr ))); } return Err(FoxhuntError::Docker(format!( "Docker push failed with exit code {:?}:\n{}", status.code(), stderr ))); } utils::success(&format!("Successfully pushed: {}", tag)); Ok(()) } /// Check if user is logged in to Docker registry #[allow(dead_code)] pub(crate) fn check_registry_auth(registry: &str) -> Result { // Try to get credentials from Docker config let output = Command::new("docker") .args(["login", "--help"]) .output() .map_err(|e| FoxhuntError::Docker(format!("Failed to check Docker login status: {}", e)))?; if !output.status.success() { return Ok(false); } // For simplicity, we assume if docker login command exists, auth is possible // A more robust check would parse ~/.docker/config.json tracing::debug!("Registry auth check for: {}", registry); Ok(true) } #[cfg(test)] mod tests { use super::*; #[test] fn test_check_registry_auth() { // This will succeed or fail depending on Docker installation let result = check_registry_auth("docker.io"); assert!(result.is_ok()); } }