mirror of
https://github.com/bahdotsh/wrkflw.git
synced 2026-02-23 19:39:42 +01:00
fix: fixed the issues in viewing step details in non verbose mode
This commit is contained in:
49
Cargo.lock
generated
49
Cargo.lock
generated
@@ -798,6 +798,15 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
@@ -1172,6 +1181,12 @@ dependencies = [
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.3"
|
||||
@@ -1721,6 +1736,7 @@ name = "runtime"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures",
|
||||
"logging",
|
||||
"models",
|
||||
"once_cell",
|
||||
@@ -1728,6 +1744,8 @@ dependencies = [
|
||||
"serde_yaml",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"utils",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1736,6 +1754,19 @@ version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.3"
|
||||
@@ -1745,7 +1776,7 @@ dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"linux-raw-sys 0.9.3",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -2064,7 +2095,7 @@ dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"rustix 1.0.3",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -2449,6 +2480,18 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"once_cell",
|
||||
"rustix 0.38.44",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@@ -2728,7 +2771,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rustix",
|
||||
"rustix 1.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#[allow(unused_imports)]
|
||||
use bollard::Docker;
|
||||
use futures::future;
|
||||
use regex;
|
||||
@@ -269,6 +270,12 @@ fn create_gitlab_context(pipeline: &Pipeline, workspace_dir: &Path) -> HashMap<S
|
||||
workspace_dir.to_string_lossy().to_string(),
|
||||
);
|
||||
|
||||
// Also add the workspace as the GitHub workspace for compatibility with emulation runtime
|
||||
env_context.insert(
|
||||
"GITHUB_WORKSPACE".to_string(),
|
||||
workspace_dir.to_string_lossy().to_string(),
|
||||
);
|
||||
|
||||
// Add global variables from the pipeline
|
||||
if let Some(variables) = &pipeline.variables {
|
||||
for (key, value) in variables {
|
||||
@@ -612,219 +619,16 @@ async fn execute_job(ctx: JobExecutionContext<'_>) -> Result<JobResult, Executio
|
||||
let job_dir = tempfile::tempdir()
|
||||
.map_err(|e| ExecutionError::Execution(format!("Failed to create job directory: {}", e)))?;
|
||||
|
||||
// Try to get a Docker client if using Docker and services exist
|
||||
let docker_client = if !job.services.is_empty() {
|
||||
match Docker::connect_with_local_defaults() {
|
||||
Ok(client) => Some(client),
|
||||
Err(e) => {
|
||||
logging::error(&format!("Failed to connect to Docker: {}", e));
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Create a Docker network for this job if we have services
|
||||
let network_id = if !job.services.is_empty() && docker_client.is_some() {
|
||||
let docker = match docker_client.as_ref() {
|
||||
Some(client) => client,
|
||||
None => {
|
||||
return Err(ExecutionError::Runtime(
|
||||
"Docker client is required but not available".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
match docker::create_job_network(docker).await {
|
||||
Ok(id) => {
|
||||
logging::info(&format!(
|
||||
"Created network {} for job '{}'",
|
||||
id, ctx.job_name
|
||||
));
|
||||
Some(id)
|
||||
}
|
||||
Err(e) => {
|
||||
logging::error(&format!(
|
||||
"Failed to create network for job '{}': {}",
|
||||
ctx.job_name, e
|
||||
));
|
||||
return Err(ExecutionError::Runtime(format!(
|
||||
"Failed to create network: {}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Start service containers if any
|
||||
let mut service_containers = Vec::new();
|
||||
|
||||
if !job.services.is_empty() {
|
||||
if docker_client.is_none() {
|
||||
logging::error("Services are only supported with Docker runtime");
|
||||
return Err(ExecutionError::Runtime(
|
||||
"Services require Docker runtime".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
logging::info(&format!(
|
||||
"Starting {} service containers for job '{}'",
|
||||
job.services.len(),
|
||||
ctx.job_name
|
||||
));
|
||||
|
||||
let docker = match docker_client.as_ref() {
|
||||
Some(client) => client,
|
||||
None => {
|
||||
return Err(ExecutionError::Runtime(
|
||||
"Docker client is required but not available".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
#[allow(unused_variables, unused_assignments)]
|
||||
for (service_name, service_config) in &job.services {
|
||||
logging::info(&format!(
|
||||
"Starting service '{}' with image '{}'",
|
||||
service_name, service_config.image
|
||||
));
|
||||
|
||||
// Prepare container configuration
|
||||
let container_name = format!("wrkflw-service-{}-{}", ctx.job_name, service_name);
|
||||
|
||||
// Map ports if specified
|
||||
let mut port_bindings = HashMap::new();
|
||||
if let Some(ports) = &service_config.ports {
|
||||
for port_spec in ports {
|
||||
// Parse port spec like "8080:80"
|
||||
let parts: Vec<&str> = port_spec.split(':').collect();
|
||||
if parts.len() == 2 {
|
||||
let host_port = parts[0];
|
||||
let container_port = parts[1];
|
||||
|
||||
let port_binding = bollard::models::PortBinding {
|
||||
host_ip: Some("0.0.0.0".to_string()),
|
||||
host_port: Some(host_port.to_string()),
|
||||
};
|
||||
|
||||
let key = format!("{}/tcp", container_port);
|
||||
port_bindings.insert(key, Some(vec![port_binding]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert environment variables
|
||||
let env_vars: Vec<String> = service_config
|
||||
.env
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{}={}", k, v))
|
||||
.collect();
|
||||
|
||||
// Create container options
|
||||
let create_opts = bollard::container::CreateContainerOptions {
|
||||
name: container_name,
|
||||
platform: None,
|
||||
};
|
||||
|
||||
// Host configuration
|
||||
let host_config = bollard::models::HostConfig {
|
||||
port_bindings: Some(port_bindings),
|
||||
network_mode: network_id.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Container configuration
|
||||
let config = bollard::container::Config {
|
||||
image: Some(service_config.image.clone()),
|
||||
env: Some(env_vars),
|
||||
host_config: Some(host_config),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Log the network connection
|
||||
if network_id.is_some() {
|
||||
logging::info(&format!(
|
||||
"Service '{}' connected to network via host_config",
|
||||
service_name
|
||||
));
|
||||
}
|
||||
|
||||
match docker.create_container(Some(create_opts), config).await {
|
||||
Ok(response) => {
|
||||
let container_id = response.id;
|
||||
|
||||
// Track the container for cleanup
|
||||
docker::track_container(&container_id);
|
||||
service_containers.push(container_id.clone());
|
||||
|
||||
// Start the container
|
||||
match docker.start_container::<String>(&container_id, None).await {
|
||||
Ok(_) => {
|
||||
logging::info(&format!("Started service container: {}", container_id));
|
||||
|
||||
// Add service address to environment
|
||||
job_env.insert(
|
||||
format!("{}_HOST", service_name.to_uppercase()),
|
||||
service_name.clone(),
|
||||
);
|
||||
|
||||
job_logs.push_str(&format!(
|
||||
"Started service '{}' with container ID: {}\n",
|
||||
service_name, container_id
|
||||
));
|
||||
}
|
||||
Err(e) => {
|
||||
let error_msg = format!(
|
||||
"Failed to start service container '{}': {}",
|
||||
service_name, e
|
||||
);
|
||||
logging::error(&error_msg);
|
||||
|
||||
// Clean up the created container
|
||||
let _ = docker.remove_container(&container_id, None).await;
|
||||
|
||||
// Clean up network if created
|
||||
if let Some(net_id) = &network_id {
|
||||
let _ = docker.remove_network(net_id).await;
|
||||
docker::untrack_network(net_id);
|
||||
}
|
||||
|
||||
return Err(ExecutionError::Runtime(error_msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let error_msg = format!(
|
||||
"Failed to create service container '{}': {}",
|
||||
service_name, e
|
||||
);
|
||||
logging::error(&error_msg);
|
||||
|
||||
// Clean up network if created
|
||||
if let Some(net_id) = &network_id {
|
||||
let _ = docker.remove_network(net_id).await;
|
||||
docker::untrack_network(net_id);
|
||||
}
|
||||
|
||||
return Err(ExecutionError::Runtime(error_msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Give services a moment to start up
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
|
||||
}
|
||||
|
||||
// Prepare the runner environment
|
||||
let runner_image = get_runner_image(&job.runs_on);
|
||||
prepare_runner_image(&runner_image, ctx.runtime, ctx.verbose).await?;
|
||||
|
||||
// Copy project files to workspace
|
||||
// Get the current project directory
|
||||
let current_dir = std::env::current_dir().map_err(|e| {
|
||||
ExecutionError::Execution(format!("Failed to get current directory: {}", e))
|
||||
})?;
|
||||
|
||||
// Copy project files to the job workspace directory
|
||||
logging::info(&format!(
|
||||
"Copying project files to job workspace: {}",
|
||||
job_dir.path().display()
|
||||
));
|
||||
copy_directory_contents(¤t_dir, job_dir.path())?;
|
||||
|
||||
logging::info(&format!("Executing job: {}", ctx.job_name));
|
||||
@@ -840,7 +644,7 @@ async fn execute_job(ctx: JobExecutionContext<'_>) -> Result<JobResult, Executio
|
||||
working_dir: job_dir.path(),
|
||||
runtime: ctx.runtime,
|
||||
workflow: ctx.workflow,
|
||||
runner_image: &runner_image,
|
||||
runner_image: &get_runner_image(&job.runs_on),
|
||||
verbose: ctx.verbose,
|
||||
matrix_combination: &None,
|
||||
})
|
||||
@@ -889,50 +693,6 @@ async fn execute_job(ctx: JobExecutionContext<'_>) -> Result<JobResult, Executio
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up service containers
|
||||
if !service_containers.is_empty() && docker_client.is_some() {
|
||||
let docker = match docker_client.as_ref() {
|
||||
Some(client) => client,
|
||||
None => {
|
||||
return Err(ExecutionError::Runtime(
|
||||
"Docker client is required but not available".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
for container_id in &service_containers {
|
||||
logging::info(&format!("Stopping service container: {}", container_id));
|
||||
|
||||
let _ = docker.stop_container(container_id, None).await;
|
||||
let _ = docker.remove_container(container_id, None).await;
|
||||
|
||||
// Untrack container since we've explicitly removed it
|
||||
docker::untrack_container(container_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up network if created
|
||||
if let Some(net_id) = &network_id {
|
||||
if docker_client.is_some() {
|
||||
let docker = match docker_client.as_ref() {
|
||||
Some(client) => client,
|
||||
None => {
|
||||
return Err(ExecutionError::Runtime(
|
||||
"Docker client is required but not available".to_string(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
logging::info(&format!("Removing network: {}", net_id));
|
||||
if let Err(e) = docker.remove_network(net_id).await {
|
||||
logging::error(&format!("Failed to remove network {}: {}", net_id, e));
|
||||
}
|
||||
|
||||
// Untrack network since we've explicitly removed it
|
||||
docker::untrack_network(net_id);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(JobResult {
|
||||
name: ctx.job_name.to_string(),
|
||||
status: if job_success {
|
||||
@@ -1055,14 +815,16 @@ async fn execute_matrix_job(
|
||||
let job_dir = tempfile::tempdir()
|
||||
.map_err(|e| ExecutionError::Execution(format!("Failed to create job directory: {}", e)))?;
|
||||
|
||||
// Prepare the runner
|
||||
let runner_image = get_runner_image(&job_template.runs_on);
|
||||
prepare_runner_image(&runner_image, runtime, verbose).await?;
|
||||
|
||||
// Copy project files to workspace
|
||||
// Get the current project directory
|
||||
let current_dir = std::env::current_dir().map_err(|e| {
|
||||
ExecutionError::Execution(format!("Failed to get current directory: {}", e))
|
||||
})?;
|
||||
|
||||
// Copy project files to the job workspace directory
|
||||
logging::info(&format!(
|
||||
"Copying project files to job workspace: {}",
|
||||
job_dir.path().display()
|
||||
));
|
||||
copy_directory_contents(¤t_dir, job_dir.path())?;
|
||||
|
||||
let job_success = if job_template.steps.is_empty() {
|
||||
@@ -1078,7 +840,7 @@ async fn execute_matrix_job(
|
||||
working_dir: job_dir.path(),
|
||||
runtime,
|
||||
workflow,
|
||||
runner_image: &runner_image,
|
||||
runner_image: &get_runner_image(&job_template.runs_on),
|
||||
verbose,
|
||||
matrix_combination: &Some(combination.values.clone()),
|
||||
})
|
||||
@@ -1918,6 +1680,7 @@ fn get_runner_image(runs_on: &str) -> String {
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn prepare_runner_image(
|
||||
image: &str,
|
||||
runtime: &dyn ContainerRuntime,
|
||||
@@ -1947,7 +1710,7 @@ async fn prepare_runner_image(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Extract language and version information from an image name
|
||||
#[allow(dead_code)]
|
||||
fn extract_language_info(image: &str) -> Option<(&'static str, Option<&str>)> {
|
||||
let image_lower = image.to_lowercase();
|
||||
|
||||
|
||||
@@ -8,12 +8,15 @@ license.workspace = true
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
logging = { path = "../logging" }
|
||||
logging = { path = "../logging", version = "0.4.0" }
|
||||
|
||||
# External dependencies
|
||||
async-trait.workspace = true
|
||||
once_cell.workspace = true
|
||||
once_cell = "1.19"
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
tempfile.workspace = true
|
||||
tempfile = "3.9"
|
||||
tokio.workspace = true
|
||||
futures = "0.3"
|
||||
utils = { path = "../utils", version = "0.4.0" }
|
||||
which = "4.4"
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::sync::Mutex;
|
||||
use tempfile::TempDir;
|
||||
use which;
|
||||
|
||||
// Global collection of resources to clean up
|
||||
static EMULATION_WORKSPACES: Lazy<Mutex<Vec<PathBuf>>> = Lazy::new(|| Mutex::new(Vec::new()));
|
||||
@@ -160,29 +161,189 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
command_str.push_str(part);
|
||||
}
|
||||
|
||||
// Log the command being executed
|
||||
// Log more detailed debugging information
|
||||
logging::info(&format!("Executing command in container: {}", command_str));
|
||||
logging::info(&format!("Working directory: {}", working_dir.display()));
|
||||
logging::info(&format!("Command length: {}", command.len()));
|
||||
|
||||
// Special handling for Rust/Cargo actions
|
||||
if command_str.contains("rust") || command_str.contains("cargo") {
|
||||
logging::debug(&format!("Executing Rust command: {}", command_str));
|
||||
if command.is_empty() {
|
||||
return Err(ContainerError::ContainerExecution(
|
||||
"Empty command array".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut cmd = Command::new("cargo");
|
||||
let parts = command_str.split_whitespace().collect::<Vec<&str>>();
|
||||
// Print each command part separately for debugging
|
||||
for (i, part) in command.iter().enumerate() {
|
||||
logging::info(&format!("Command part {}: '{}'", i, part));
|
||||
}
|
||||
|
||||
let current_dir = working_dir.to_str().unwrap_or(".");
|
||||
cmd.current_dir(current_dir);
|
||||
// Log environment variables
|
||||
logging::info("Environment variables:");
|
||||
for (key, value) in env_vars {
|
||||
logging::info(&format!(" {}={}", key, value));
|
||||
}
|
||||
|
||||
// Find actual working directory - determine if we should use the current directory instead
|
||||
let actual_working_dir: PathBuf = if !working_dir.exists() {
|
||||
// Look for GITHUB_WORKSPACE or CI_PROJECT_DIR in env_vars
|
||||
let mut workspace_path = None;
|
||||
for (key, value) in env_vars {
|
||||
if *key == "GITHUB_WORKSPACE" || *key == "CI_PROJECT_DIR" {
|
||||
workspace_path = Some(PathBuf::from(value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If found, use that as the working directory
|
||||
if let Some(path) = workspace_path {
|
||||
if path.exists() {
|
||||
logging::info(&format!(
|
||||
"Using environment-defined workspace: {}",
|
||||
path.display()
|
||||
));
|
||||
path
|
||||
} else {
|
||||
// Fallback to current directory
|
||||
let current_dir =
|
||||
std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
||||
logging::info(&format!(
|
||||
"Using current directory: {}",
|
||||
current_dir.display()
|
||||
));
|
||||
current_dir
|
||||
}
|
||||
} else {
|
||||
// Fallback to current directory
|
||||
let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
||||
logging::info(&format!(
|
||||
"Using current directory: {}",
|
||||
current_dir.display()
|
||||
));
|
||||
current_dir
|
||||
}
|
||||
} else {
|
||||
working_dir.to_path_buf()
|
||||
};
|
||||
|
||||
logging::info(&format!(
|
||||
"Using actual working directory: {}",
|
||||
actual_working_dir.display()
|
||||
));
|
||||
|
||||
// Check if path contains the command (for shell script execution)
|
||||
let command_path = which::which(command[0]);
|
||||
match &command_path {
|
||||
Ok(path) => logging::info(&format!("Found command at: {}", path.display())),
|
||||
Err(e) => logging::error(&format!(
|
||||
"Command not found in PATH: {} - Error: {}",
|
||||
command[0], e
|
||||
)),
|
||||
}
|
||||
|
||||
// First, check if this is a simple shell command (like echo)
|
||||
if command_str.starts_with("echo ")
|
||||
|| command_str.starts_with("cp ")
|
||||
|| command_str.starts_with("mkdir ")
|
||||
|| command_str.starts_with("mv ")
|
||||
{
|
||||
logging::info("Executing as shell command");
|
||||
// Execute as a shell command
|
||||
let mut cmd = Command::new("sh");
|
||||
cmd.arg("-c");
|
||||
cmd.arg(&command_str);
|
||||
cmd.current_dir(&actual_working_dir);
|
||||
|
||||
// Add environment variables
|
||||
for (key, value) in env_vars {
|
||||
cmd.env(key, value);
|
||||
}
|
||||
|
||||
match cmd.output() {
|
||||
Ok(output_result) => {
|
||||
let exit_code = output_result.status.code().unwrap_or(-1);
|
||||
let output = String::from_utf8_lossy(&output_result.stdout).to_string();
|
||||
let error = String::from_utf8_lossy(&output_result.stderr).to_string();
|
||||
|
||||
logging::debug(&format!(
|
||||
"Shell command completed with exit code: {}",
|
||||
exit_code
|
||||
));
|
||||
|
||||
if exit_code != 0 {
|
||||
let mut error_details = format!(
|
||||
"Command failed with exit code: {}\nCommand: {}\n\nError output:\n{}",
|
||||
exit_code, command_str, error
|
||||
);
|
||||
|
||||
// Add environment variables to error details
|
||||
error_details.push_str("\n\nEnvironment variables:\n");
|
||||
for (key, value) in env_vars {
|
||||
if key.starts_with("GITHUB_") || key.starts_with("CI_") {
|
||||
error_details.push_str(&format!("{}={}\n", key, value));
|
||||
}
|
||||
}
|
||||
|
||||
return Err(ContainerError::ContainerExecution(error_details));
|
||||
}
|
||||
|
||||
return Ok(ContainerOutput {
|
||||
stdout: output,
|
||||
stderr: error,
|
||||
exit_code,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(ContainerError::ContainerExecution(format!(
|
||||
"Failed to execute command: {}\nError: {}",
|
||||
command_str, e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for Rust/Cargo commands
|
||||
if command_str.starts_with("cargo ") || command_str.starts_with("rustup ") {
|
||||
let parts: Vec<&str> = command_str.split_whitespace().collect();
|
||||
if parts.is_empty() {
|
||||
return Err(ContainerError::ContainerExecution(
|
||||
"Empty command".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut cmd = Command::new(parts[0]);
|
||||
|
||||
// Always use the current directory for cargo/rust commands rather than the temporary directory
|
||||
let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
||||
logging::info(&format!(
|
||||
"Using project directory for Rust command: {}",
|
||||
current_dir.display()
|
||||
));
|
||||
cmd.current_dir(¤t_dir);
|
||||
|
||||
// Add environment variables
|
||||
for (key, value) in env_vars {
|
||||
// Don't use the CI_PROJECT_DIR for CARGO_HOME, use the actual project directory
|
||||
if *key == "CARGO_HOME" && value.contains("${CI_PROJECT_DIR}") {
|
||||
let cargo_home =
|
||||
value.replace("${CI_PROJECT_DIR}", ¤t_dir.to_string_lossy());
|
||||
logging::info(&format!("Setting CARGO_HOME to: {}", cargo_home));
|
||||
cmd.env(key, cargo_home);
|
||||
} else {
|
||||
cmd.env(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Add command arguments
|
||||
if parts.len() > 1 {
|
||||
cmd.args(&parts[1..]);
|
||||
}
|
||||
|
||||
logging::debug(&format!(
|
||||
"Executing Rust command: {} in {}",
|
||||
command_str,
|
||||
current_dir.display()
|
||||
));
|
||||
|
||||
match cmd.output() {
|
||||
Ok(output_result) => {
|
||||
let exit_code = output_result.status.code().unwrap_or(-1);
|
||||
@@ -200,7 +361,11 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
// Add environment variables to error details
|
||||
error_details.push_str("\n\nEnvironment variables:\n");
|
||||
for (key, value) in env_vars {
|
||||
if key.starts_with("GITHUB_") || key.starts_with("RUST") {
|
||||
if key.starts_with("GITHUB_")
|
||||
|| key.starts_with("RUST")
|
||||
|| key.starts_with("CARGO")
|
||||
|| key.starts_with("CI_")
|
||||
{
|
||||
error_details.push_str(&format!("{}={}\n", key, value));
|
||||
}
|
||||
}
|
||||
@@ -223,11 +388,11 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
// For other commands, use a shell
|
||||
// For other commands, use a shell as fallback
|
||||
let mut cmd = Command::new("sh");
|
||||
cmd.arg("-c");
|
||||
cmd.arg(&command_str);
|
||||
cmd.current_dir(working_dir.to_str().unwrap_or("."));
|
||||
cmd.current_dir(&actual_working_dir);
|
||||
|
||||
// Add environment variables
|
||||
for (key, value) in env_vars {
|
||||
@@ -251,7 +416,7 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
// Add environment variables to error details
|
||||
error_details.push_str("\n\nEnvironment variables:\n");
|
||||
for (key, value) in env_vars {
|
||||
if key.starts_with("GITHUB_") {
|
||||
if key.starts_with("GITHUB_") || key.starts_with("CI_") {
|
||||
error_details.push_str(&format!("{}={}\n", key, value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,14 +336,32 @@ async fn main() {
|
||||
if result.failure_details.is_some() {
|
||||
eprintln!("❌ Workflow execution failed:");
|
||||
if let Some(details) = result.failure_details {
|
||||
eprintln!("{}", details);
|
||||
if verbose {
|
||||
// Show full error details in verbose mode
|
||||
eprintln!("{}", details);
|
||||
} else {
|
||||
// Show simplified error info in non-verbose mode
|
||||
let simplified_error = details
|
||||
.lines()
|
||||
.filter(|line| line.contains("❌") || line.trim().starts_with("Error:"))
|
||||
.take(5) // Limit to the first 5 error lines
|
||||
.collect::<Vec<&str>>()
|
||||
.join("\n");
|
||||
|
||||
eprintln!("{}", simplified_error);
|
||||
|
||||
if details.lines().count() > 5 {
|
||||
eprintln!("\nUse --verbose flag to see full error details");
|
||||
}
|
||||
}
|
||||
}
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
println!("✅ Workflow execution completed successfully!");
|
||||
|
||||
// Print a summary of executed jobs
|
||||
if verbose {
|
||||
if true {
|
||||
// Always show job summary
|
||||
println!("\nJob summary:");
|
||||
for job in result.jobs {
|
||||
println!(
|
||||
@@ -361,18 +379,42 @@ async fn main() {
|
||||
}
|
||||
);
|
||||
|
||||
if debug {
|
||||
println!(" Steps:");
|
||||
for step in job.steps {
|
||||
println!(
|
||||
" {} {}",
|
||||
match step.status {
|
||||
executor::StepStatus::Success => "✅",
|
||||
executor::StepStatus::Failure => "❌",
|
||||
executor::StepStatus::Skipped => "⏭️",
|
||||
},
|
||||
step.name
|
||||
);
|
||||
// Always show steps, not just in debug mode
|
||||
println!(" Steps:");
|
||||
for step in job.steps {
|
||||
let step_status = match step.status {
|
||||
executor::StepStatus::Success => "✅",
|
||||
executor::StepStatus::Failure => "❌",
|
||||
executor::StepStatus::Skipped => "⏭️",
|
||||
};
|
||||
|
||||
println!(" {} {}", step_status, step.name);
|
||||
|
||||
// If step failed and we're not in verbose mode, show condensed error info
|
||||
if step.status == executor::StepStatus::Failure && !verbose {
|
||||
// Extract error information from step output
|
||||
let error_lines = step
|
||||
.output
|
||||
.lines()
|
||||
.filter(|line| {
|
||||
line.contains("error:")
|
||||
|| line.contains("Error:")
|
||||
|| line.trim().starts_with("Exit code:")
|
||||
|| line.contains("failed")
|
||||
})
|
||||
.take(3) // Limit to 3 most relevant error lines
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
if !error_lines.is_empty() {
|
||||
println!(" Error details:");
|
||||
for line in error_lines {
|
||||
println!(" {}", line.trim());
|
||||
}
|
||||
|
||||
if step.output.lines().count() > 3 {
|
||||
println!(" (Use --verbose for full output)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user