This commit is contained in:
bahdotsh
2025-08-09 17:03:03 +05:30
parent 181b5c5463
commit f0b6633cb8
41 changed files with 905 additions and 691 deletions

372
Cargo.lock generated
View File

@@ -486,46 +486,6 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "evaluator"
version = "0.5.0"
dependencies = [
"colored",
"models",
"serde_yaml",
"validators",
]
[[package]]
name = "executor"
version = "0.5.0"
dependencies = [
"async-trait",
"bollard",
"chrono",
"dirs",
"futures",
"futures-util",
"lazy_static",
"logging",
"matrix",
"models",
"num_cpus",
"once_cell",
"parser",
"regex",
"runtime",
"serde",
"serde_json",
"serde_yaml",
"tar",
"tempfile",
"thiserror",
"tokio",
"utils",
"uuid",
]
[[package]] [[package]]
name = "fancy-regex" name = "fancy-regex"
version = "0.11.0" version = "0.11.0"
@@ -714,35 +674,6 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "github"
version = "0.5.0"
dependencies = [
"lazy_static",
"models",
"regex",
"reqwest",
"serde",
"serde_json",
"serde_yaml",
"thiserror",
]
[[package]]
name = "gitlab"
version = "0.5.0"
dependencies = [
"lazy_static",
"models",
"regex",
"reqwest",
"serde",
"serde_json",
"serde_yaml",
"thiserror",
"urlencoding",
]
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.26" version = "0.3.26"
@@ -1215,28 +1146,6 @@ version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "logging"
version = "0.5.0"
dependencies = [
"chrono",
"models",
"once_cell",
"serde",
"serde_yaml",
]
[[package]]
name = "matrix"
version = "0.5.0"
dependencies = [
"indexmap 2.8.0",
"models",
"serde",
"serde_yaml",
"thiserror",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"
@@ -1281,16 +1190,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "models"
version = "0.5.0"
dependencies = [
"serde",
"serde_json",
"serde_yaml",
"thiserror",
]
[[package]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.14" version = "0.2.14"
@@ -1511,20 +1410,6 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "parser"
version = "0.5.0"
dependencies = [
"jsonschema",
"matrix",
"models",
"serde",
"serde_json",
"serde_yaml",
"tempfile",
"thiserror",
]
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.15" version = "1.0.15"
@@ -1731,23 +1616,6 @@ dependencies = [
"winreg", "winreg",
] ]
[[package]]
name = "runtime"
version = "0.5.0"
dependencies = [
"async-trait",
"futures",
"logging",
"models",
"once_cell",
"serde",
"serde_yaml",
"tempfile",
"tokio",
"utils",
"which",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.24" version = "0.1.24"
@@ -2243,28 +2111,6 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "ui"
version = "0.5.0"
dependencies = [
"chrono",
"crossterm 0.26.1",
"evaluator",
"executor",
"futures",
"github",
"logging",
"models",
"ratatui",
"regex",
"reqwest",
"serde",
"serde_json",
"serde_yaml",
"tokio",
"utils",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.18" version = "1.0.18"
@@ -2324,16 +2170,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "utils"
version = "0.5.0"
dependencies = [
"models",
"nix",
"serde",
"serde_yaml",
]
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.16.0" version = "1.16.0"
@@ -2343,16 +2179,6 @@ dependencies = [
"getrandom 0.3.2", "getrandom 0.3.2",
] ]
[[package]]
name = "validators"
version = "0.5.0"
dependencies = [
"matrix",
"models",
"serde",
"serde_yaml",
]
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@@ -2727,41 +2553,215 @@ dependencies = [
"colored", "colored",
"crossterm 0.26.1", "crossterm 0.26.1",
"dirs", "dirs",
"evaluator",
"executor",
"futures", "futures",
"futures-util", "futures-util",
"github",
"gitlab",
"indexmap 2.8.0", "indexmap 2.8.0",
"itertools", "itertools",
"lazy_static", "lazy_static",
"libc", "libc",
"log", "log",
"logging",
"matrix",
"models",
"nix", "nix",
"num_cpus", "num_cpus",
"once_cell", "once_cell",
"parser",
"ratatui", "ratatui",
"rayon", "rayon",
"regex", "regex",
"reqwest", "reqwest",
"runtime",
"serde", "serde",
"serde_json", "serde_json",
"serde_yaml", "serde_yaml",
"tempfile", "tempfile",
"thiserror", "thiserror",
"tokio", "tokio",
"ui",
"urlencoding", "urlencoding",
"utils",
"uuid", "uuid",
"validators",
"walkdir", "walkdir",
"wrkflw-evaluator",
"wrkflw-executor",
"wrkflw-github",
"wrkflw-gitlab",
"wrkflw-logging",
"wrkflw-matrix",
"wrkflw-models",
"wrkflw-parser",
"wrkflw-runtime",
"wrkflw-ui",
"wrkflw-utils",
"wrkflw-validators",
]
[[package]]
name = "wrkflw-evaluator"
version = "0.5.0"
dependencies = [
"colored",
"serde_yaml",
"wrkflw-models",
"wrkflw-validators",
]
[[package]]
name = "wrkflw-executor"
version = "0.5.0"
dependencies = [
"async-trait",
"bollard",
"chrono",
"dirs",
"futures",
"futures-util",
"lazy_static",
"num_cpus",
"once_cell",
"regex",
"serde",
"serde_json",
"serde_yaml",
"tar",
"tempfile",
"thiserror",
"tokio",
"uuid",
"wrkflw-logging",
"wrkflw-matrix",
"wrkflw-models",
"wrkflw-parser",
"wrkflw-runtime",
"wrkflw-utils",
]
[[package]]
name = "wrkflw-github"
version = "0.5.0"
dependencies = [
"lazy_static",
"regex",
"reqwest",
"serde",
"serde_json",
"serde_yaml",
"thiserror",
"wrkflw-models",
]
[[package]]
name = "wrkflw-gitlab"
version = "0.5.0"
dependencies = [
"lazy_static",
"regex",
"reqwest",
"serde",
"serde_json",
"serde_yaml",
"thiserror",
"urlencoding",
"wrkflw-models",
]
[[package]]
name = "wrkflw-logging"
version = "0.5.0"
dependencies = [
"chrono",
"once_cell",
"serde",
"serde_yaml",
"wrkflw-models",
]
[[package]]
name = "wrkflw-matrix"
version = "0.5.0"
dependencies = [
"indexmap 2.8.0",
"serde",
"serde_yaml",
"thiserror",
"wrkflw-models",
]
[[package]]
name = "wrkflw-models"
version = "0.5.0"
dependencies = [
"serde",
"serde_json",
"serde_yaml",
"thiserror",
]
[[package]]
name = "wrkflw-parser"
version = "0.5.0"
dependencies = [
"jsonschema",
"serde",
"serde_json",
"serde_yaml",
"tempfile",
"thiserror",
"wrkflw-matrix",
"wrkflw-models",
]
[[package]]
name = "wrkflw-runtime"
version = "0.5.0"
dependencies = [
"async-trait",
"futures",
"once_cell",
"serde",
"serde_yaml",
"tempfile",
"tokio",
"which",
"wrkflw-logging",
"wrkflw-models",
"wrkflw-utils",
]
[[package]]
name = "wrkflw-ui"
version = "0.5.0"
dependencies = [
"chrono",
"crossterm 0.26.1",
"futures",
"ratatui",
"regex",
"reqwest",
"serde",
"serde_json",
"serde_yaml",
"tokio",
"wrkflw-evaluator",
"wrkflw-executor",
"wrkflw-github",
"wrkflw-logging",
"wrkflw-models",
"wrkflw-utils",
]
[[package]]
name = "wrkflw-utils"
version = "0.5.0"
dependencies = [
"nix",
"serde",
"serde_yaml",
"wrkflw-models",
]
[[package]]
name = "wrkflw-validators"
version = "0.5.0"
dependencies = [
"serde",
"serde_yaml",
"wrkflw-matrix",
"wrkflw-models",
] ]
[[package]] [[package]]

View File

@@ -1,14 +1,19 @@
[package] [package]
name = "evaluator" name = "wrkflw-evaluator"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
description = "Workflow evaluation for wrkflw" description = "Workflow evaluation functionality for wrkflw execution engine"
license.workspace = true license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies] [dependencies]
# Internal crates # Internal crates
models = { path = "../models" } wrkflw-models = { path = "../models", version = "0.5.0" }
validators = { path = "../validators" } wrkflw-validators = { path = "../validators", version = "0.5.0" }
# External dependencies # External dependencies
colored.workspace = true colored.workspace = true

View File

@@ -3,8 +3,8 @@ use serde_yaml::{self, Value};
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use models::ValidationResult; use wrkflw_models::ValidationResult;
use validators::{validate_jobs, validate_triggers}; use wrkflw_validators::{validate_jobs, validate_triggers};
pub fn evaluate_workflow_file(path: &Path, verbose: bool) -> Result<ValidationResult, String> { pub fn evaluate_workflow_file(path: &Path, verbose: bool) -> Result<ValidationResult, String> {
let content = fs::read_to_string(path).map_err(|e| format!("Failed to read file: {}", e))?; let content = fs::read_to_string(path).map_err(|e| format!("Failed to read file: {}", e))?;

View File

@@ -1,18 +1,23 @@
[package] [package]
name = "executor" name = "wrkflw-executor"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
description = "Workflow executor for wrkflw" description = "Workflow execution engine for wrkflw"
license.workspace = true license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies] [dependencies]
# Internal crates # Internal crates
models = { path = "../models" } wrkflw-models = { path = "../models", version = "0.5.0" }
parser = { path = "../parser" } wrkflw-parser = { path = "../parser", version = "0.5.0" }
runtime = { path = "../runtime" } wrkflw-runtime = { path = "../runtime", version = "0.5.0" }
logging = { path = "../logging" } wrkflw-logging = { path = "../logging", version = "0.5.0" }
matrix = { path = "../matrix" } wrkflw-matrix = { path = "../matrix", version = "0.5.0" }
utils = { path = "../utils" } wrkflw-utils = { path = "../utils", version = "0.5.0" }
# External dependencies # External dependencies
async-trait.workspace = true async-trait.workspace = true

View File

@@ -1,5 +1,5 @@
use parser::workflow::WorkflowDefinition;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use wrkflw_parser::workflow::WorkflowDefinition;
pub fn resolve_dependencies(workflow: &WorkflowDefinition) -> Result<Vec<Vec<String>>, String> { pub fn resolve_dependencies(workflow: &WorkflowDefinition) -> Result<Vec<Vec<String>>, String> {
let jobs = &workflow.jobs; let jobs = &workflow.jobs;

View File

@@ -6,14 +6,14 @@ use bollard::{
Docker, Docker,
}; };
use futures_util::StreamExt; use futures_util::StreamExt;
use logging;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use runtime::container::{ContainerError, ContainerOutput, ContainerRuntime};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::sync::Mutex; use std::sync::Mutex;
use utils; use wrkflw_logging;
use utils::fd; use wrkflw_runtime::container::{ContainerError, ContainerOutput, ContainerRuntime};
use wrkflw_utils;
use wrkflw_utils::fd;
static RUNNING_CONTAINERS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new())); static RUNNING_CONTAINERS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
static CREATED_NETWORKS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new())); static CREATED_NETWORKS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
@@ -50,7 +50,7 @@ impl DockerRuntime {
match CUSTOMIZED_IMAGES.lock() { match CUSTOMIZED_IMAGES.lock() {
Ok(images) => images.get(&key).cloned(), Ok(images) => images.get(&key).cloned(),
Err(e) => { Err(e) => {
logging::error(&format!("Failed to acquire lock: {}", e)); wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
None None
} }
} }
@@ -62,7 +62,7 @@ impl DockerRuntime {
if let Err(e) = CUSTOMIZED_IMAGES.lock().map(|mut images| { if let Err(e) = CUSTOMIZED_IMAGES.lock().map(|mut images| {
images.insert(key, new_image.to_string()); images.insert(key, new_image.to_string());
}) { }) {
logging::error(&format!("Failed to acquire lock: {}", e)); wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
} }
} }
@@ -72,7 +72,7 @@ impl DockerRuntime {
let image_keys = match CUSTOMIZED_IMAGES.lock() { let image_keys = match CUSTOMIZED_IMAGES.lock() {
Ok(keys) => keys, Ok(keys) => keys,
Err(e) => { Err(e) => {
logging::error(&format!("Failed to acquire lock: {}", e)); wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
return None; return None;
} }
}; };
@@ -107,7 +107,7 @@ impl DockerRuntime {
match CUSTOMIZED_IMAGES.lock() { match CUSTOMIZED_IMAGES.lock() {
Ok(images) => images.get(&key).cloned(), Ok(images) => images.get(&key).cloned(),
Err(e) => { Err(e) => {
logging::error(&format!("Failed to acquire lock: {}", e)); wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
None None
} }
} }
@@ -134,7 +134,7 @@ impl DockerRuntime {
if let Err(e) = CUSTOMIZED_IMAGES.lock().map(|mut images| { if let Err(e) = CUSTOMIZED_IMAGES.lock().map(|mut images| {
images.insert(key, new_image.to_string()); images.insert(key, new_image.to_string());
}) { }) {
logging::error(&format!("Failed to acquire lock: {}", e)); wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
} }
} }
@@ -318,7 +318,7 @@ pub fn is_available() -> bool {
} }
} }
Err(_) => { Err(_) => {
logging::debug("Docker CLI is not available"); wrkflw_logging::debug("Docker CLI is not available");
return false; return false;
} }
} }
@@ -331,7 +331,7 @@ pub fn is_available() -> bool {
{ {
Ok(rt) => rt, Ok(rt) => rt,
Err(e) => { Err(e) => {
logging::error(&format!( wrkflw_logging::error(&format!(
"Failed to create runtime for Docker availability check: {}", "Failed to create runtime for Docker availability check: {}",
e e
)); ));
@@ -352,17 +352,25 @@ pub fn is_available() -> bool {
{ {
Ok(Ok(_)) => true, Ok(Ok(_)) => true,
Ok(Err(e)) => { Ok(Err(e)) => {
logging::debug(&format!("Docker daemon ping failed: {}", e)); wrkflw_logging::debug(&format!(
"Docker daemon ping failed: {}",
e
));
false false
} }
Err(_) => { Err(_) => {
logging::debug("Docker daemon ping timed out after 1 second"); wrkflw_logging::debug(
"Docker daemon ping timed out after 1 second",
);
false false
} }
} }
} }
Err(e) => { Err(e) => {
logging::debug(&format!("Docker daemon connection failed: {}", e)); wrkflw_logging::debug(&format!(
"Docker daemon connection failed: {}",
e
));
false false
} }
} }
@@ -371,7 +379,7 @@ pub fn is_available() -> bool {
{ {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::debug("Docker availability check timed out"); wrkflw_logging::debug("Docker availability check timed out");
false false
} }
} }
@@ -379,7 +387,9 @@ pub fn is_available() -> bool {
}) { }) {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::debug("Failed to redirect stderr when checking Docker availability"); wrkflw_logging::debug(
"Failed to redirect stderr when checking Docker availability",
);
false false
} }
} }
@@ -393,7 +403,7 @@ pub fn is_available() -> bool {
return match handle.join() { return match handle.join() {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::warning("Docker availability check thread panicked"); wrkflw_logging::warning("Docker availability check thread panicked");
false false
} }
}; };
@@ -401,7 +411,9 @@ pub fn is_available() -> bool {
std::thread::sleep(std::time::Duration::from_millis(50)); std::thread::sleep(std::time::Duration::from_millis(50));
} }
logging::warning("Docker availability check timed out, assuming Docker is not available"); wrkflw_logging::warning(
"Docker availability check timed out, assuming Docker is not available",
);
false false
} }
@@ -444,19 +456,19 @@ pub async fn cleanup_resources(docker: &Docker) {
tokio::join!(cleanup_containers(docker), cleanup_networks(docker)); tokio::join!(cleanup_containers(docker), cleanup_networks(docker));
if let Err(e) = container_result { if let Err(e) = container_result {
logging::error(&format!("Error during container cleanup: {}", e)); wrkflw_logging::error(&format!("Error during container cleanup: {}", e));
} }
if let Err(e) = network_result { if let Err(e) = network_result {
logging::error(&format!("Error during network cleanup: {}", e)); wrkflw_logging::error(&format!("Error during network cleanup: {}", e));
} }
}) })
.await .await
{ {
Ok(_) => logging::debug("Docker cleanup completed within timeout"), Ok(_) => wrkflw_logging::debug("Docker cleanup completed within timeout"),
Err(_) => { Err(_) => wrkflw_logging::warning(
logging::warning("Docker cleanup timed out, some resources may not have been removed") "Docker cleanup timed out, some resources may not have been removed",
} ),
} }
} }
@@ -468,7 +480,7 @@ pub async fn cleanup_containers(docker: &Docker) -> Result<(), String> {
match RUNNING_CONTAINERS.try_lock() { match RUNNING_CONTAINERS.try_lock() {
Ok(containers) => containers.clone(), Ok(containers) => containers.clone(),
Err(_) => { Err(_) => {
logging::error("Could not acquire container lock for cleanup"); wrkflw_logging::error("Could not acquire container lock for cleanup");
vec![] vec![]
} }
} }
@@ -477,7 +489,7 @@ pub async fn cleanup_containers(docker: &Docker) -> Result<(), String> {
{ {
Ok(containers) => containers, Ok(containers) => containers,
Err(_) => { Err(_) => {
logging::error("Timeout while trying to get containers for cleanup"); wrkflw_logging::error("Timeout while trying to get containers for cleanup");
vec![] vec![]
} }
}; };
@@ -486,7 +498,7 @@ pub async fn cleanup_containers(docker: &Docker) -> Result<(), String> {
return Ok(()); return Ok(());
} }
logging::info(&format!( wrkflw_logging::info(&format!(
"Cleaning up {} containers", "Cleaning up {} containers",
containers_to_cleanup.len() containers_to_cleanup.len()
)); ));
@@ -500,11 +512,14 @@ pub async fn cleanup_containers(docker: &Docker) -> Result<(), String> {
) )
.await .await
{ {
Ok(Ok(_)) => logging::debug(&format!("Stopped container: {}", container_id)), Ok(Ok(_)) => wrkflw_logging::debug(&format!("Stopped container: {}", container_id)),
Ok(Err(e)) => { Ok(Err(e)) => wrkflw_logging::warning(&format!(
logging::warning(&format!("Error stopping container {}: {}", container_id, e)) "Error stopping container {}: {}",
container_id, e
)),
Err(_) => {
wrkflw_logging::warning(&format!("Timeout stopping container: {}", container_id))
} }
Err(_) => logging::warning(&format!("Timeout stopping container: {}", container_id)),
} }
// Then try to remove it // Then try to remove it
@@ -514,11 +529,14 @@ pub async fn cleanup_containers(docker: &Docker) -> Result<(), String> {
) )
.await .await
{ {
Ok(Ok(_)) => logging::debug(&format!("Removed container: {}", container_id)), Ok(Ok(_)) => wrkflw_logging::debug(&format!("Removed container: {}", container_id)),
Ok(Err(e)) => { Ok(Err(e)) => wrkflw_logging::warning(&format!(
logging::warning(&format!("Error removing container {}: {}", container_id, e)) "Error removing container {}: {}",
container_id, e
)),
Err(_) => {
wrkflw_logging::warning(&format!("Timeout removing container: {}", container_id))
} }
Err(_) => logging::warning(&format!("Timeout removing container: {}", container_id)),
} }
// Always untrack the container whether or not we succeeded to avoid future cleanup attempts // Always untrack the container whether or not we succeeded to avoid future cleanup attempts
@@ -536,7 +554,7 @@ pub async fn cleanup_networks(docker: &Docker) -> Result<(), String> {
match CREATED_NETWORKS.try_lock() { match CREATED_NETWORKS.try_lock() {
Ok(networks) => networks.clone(), Ok(networks) => networks.clone(),
Err(_) => { Err(_) => {
logging::error("Could not acquire network lock for cleanup"); wrkflw_logging::error("Could not acquire network lock for cleanup");
vec![] vec![]
} }
} }
@@ -545,7 +563,7 @@ pub async fn cleanup_networks(docker: &Docker) -> Result<(), String> {
{ {
Ok(networks) => networks, Ok(networks) => networks,
Err(_) => { Err(_) => {
logging::error("Timeout while trying to get networks for cleanup"); wrkflw_logging::error("Timeout while trying to get networks for cleanup");
vec![] vec![]
} }
}; };
@@ -554,7 +572,7 @@ pub async fn cleanup_networks(docker: &Docker) -> Result<(), String> {
return Ok(()); return Ok(());
} }
logging::info(&format!( wrkflw_logging::info(&format!(
"Cleaning up {} networks", "Cleaning up {} networks",
networks_to_cleanup.len() networks_to_cleanup.len()
)); ));
@@ -566,9 +584,13 @@ pub async fn cleanup_networks(docker: &Docker) -> Result<(), String> {
) )
.await .await
{ {
Ok(Ok(_)) => logging::info(&format!("Successfully removed network: {}", network_id)), Ok(Ok(_)) => {
Ok(Err(e)) => logging::error(&format!("Error removing network {}: {}", network_id, e)), wrkflw_logging::info(&format!("Successfully removed network: {}", network_id))
Err(_) => logging::warning(&format!("Timeout removing network: {}", network_id)), }
Ok(Err(e)) => {
wrkflw_logging::error(&format!("Error removing network {}: {}", network_id, e))
}
Err(_) => wrkflw_logging::warning(&format!("Timeout removing network: {}", network_id)),
} }
// Always untrack the network whether or not we succeeded // Always untrack the network whether or not we succeeded
@@ -599,7 +621,7 @@ pub async fn create_job_network(docker: &Docker) -> Result<String, ContainerErro
})?; })?;
track_network(&network_id); track_network(&network_id);
logging::info(&format!("Created Docker network: {}", network_id)); wrkflw_logging::info(&format!("Created Docker network: {}", network_id));
Ok(network_id) Ok(network_id)
} }
@@ -615,7 +637,7 @@ impl ContainerRuntime for DockerRuntime {
volumes: &[(&Path, &Path)], volumes: &[(&Path, &Path)],
) -> Result<ContainerOutput, ContainerError> { ) -> Result<ContainerOutput, ContainerError> {
// Print detailed debugging info // Print detailed debugging info
logging::info(&format!("Docker: Running container with image: {}", image)); wrkflw_logging::info(&format!("Docker: Running container with image: {}", image));
// Add a global timeout for all Docker operations to prevent freezing // Add a global timeout for all Docker operations to prevent freezing
let timeout_duration = std::time::Duration::from_secs(360); // Increased outer timeout to 6 minutes let timeout_duration = std::time::Duration::from_secs(360); // Increased outer timeout to 6 minutes
@@ -629,7 +651,7 @@ impl ContainerRuntime for DockerRuntime {
{ {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::error("Docker operation timed out after 360 seconds"); wrkflw_logging::error("Docker operation timed out after 360 seconds");
Err(ContainerError::ContainerExecution( Err(ContainerError::ContainerExecution(
"Operation timed out".to_string(), "Operation timed out".to_string(),
)) ))
@@ -644,7 +666,7 @@ impl ContainerRuntime for DockerRuntime {
match tokio::time::timeout(timeout_duration, self.pull_image_inner(image)).await { match tokio::time::timeout(timeout_duration, self.pull_image_inner(image)).await {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::warning(&format!( wrkflw_logging::warning(&format!(
"Pull of image {} timed out, continuing with existing image", "Pull of image {} timed out, continuing with existing image",
image image
)); ));
@@ -662,7 +684,7 @@ impl ContainerRuntime for DockerRuntime {
{ {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::error(&format!( wrkflw_logging::error(&format!(
"Building image {} timed out after 120 seconds", "Building image {} timed out after 120 seconds",
tag tag
)); ));
@@ -836,9 +858,9 @@ impl DockerRuntime {
// Convert command vector to Vec<String> // Convert command vector to Vec<String>
let cmd_vec: Vec<String> = cmd.iter().map(|&s| s.to_string()).collect(); let cmd_vec: Vec<String> = cmd.iter().map(|&s| s.to_string()).collect();
logging::debug(&format!("Running command in Docker: {:?}", cmd_vec)); wrkflw_logging::debug(&format!("Running command in Docker: {:?}", cmd_vec));
logging::debug(&format!("Environment: {:?}", env)); wrkflw_logging::debug(&format!("Environment: {:?}", env));
logging::debug(&format!("Working directory: {}", working_dir.display())); wrkflw_logging::debug(&format!("Working directory: {}", working_dir.display()));
// Determine platform-specific configurations // Determine platform-specific configurations
let is_windows_image = image.contains("windows") let is_windows_image = image.contains("windows")
@@ -973,7 +995,7 @@ impl DockerRuntime {
_ => -1, _ => -1,
}, },
Err(_) => { Err(_) => {
logging::warning("Container wait operation timed out, treating as failure"); wrkflw_logging::warning("Container wait operation timed out, treating as failure");
-1 -1
} }
}; };
@@ -1003,7 +1025,7 @@ impl DockerRuntime {
} }
} }
} else { } else {
logging::warning("Retrieving container logs timed out"); wrkflw_logging::warning("Retrieving container logs timed out");
} }
// Clean up container with a timeout, but preserve on failure if configured // Clean up container with a timeout, but preserve on failure if configured
@@ -1016,7 +1038,7 @@ impl DockerRuntime {
untrack_container(&container.id); untrack_container(&container.id);
} else { } else {
// Container failed and we want to preserve it for debugging // Container failed and we want to preserve it for debugging
logging::info(&format!( wrkflw_logging::info(&format!(
"Preserving container {} for debugging (exit code: {}). Use 'docker exec -it {} bash' to inspect.", "Preserving container {} for debugging (exit code: {}). Use 'docker exec -it {} bash' to inspect.",
container.id, exit_code, container.id container.id, exit_code, container.id
)); ));
@@ -1026,13 +1048,13 @@ impl DockerRuntime {
// Log detailed information about the command execution for debugging // Log detailed information about the command execution for debugging
if exit_code != 0 { if exit_code != 0 {
logging::info(&format!( wrkflw_logging::info(&format!(
"Docker command failed with exit code: {}", "Docker command failed with exit code: {}",
exit_code exit_code
)); ));
logging::debug(&format!("Failed command: {:?}", cmd)); wrkflw_logging::debug(&format!("Failed command: {:?}", cmd));
logging::debug(&format!("Working directory: {}", working_dir.display())); wrkflw_logging::debug(&format!("Working directory: {}", working_dir.display()));
logging::debug(&format!("STDERR: {}", stderr)); wrkflw_logging::debug(&format!("STDERR: {}", stderr));
} }
Ok(ContainerOutput { Ok(ContainerOutput {

View File

@@ -13,13 +13,13 @@ use crate::dependency;
use crate::docker; use crate::docker;
use crate::environment; use crate::environment;
use crate::podman; use crate::podman;
use logging; use wrkflw_logging;
use matrix::MatrixCombination; use wrkflw_matrix::MatrixCombination;
use models::gitlab::Pipeline; use wrkflw_models::gitlab::Pipeline;
use parser::gitlab::{self, parse_pipeline}; use wrkflw_parser::gitlab::{self, parse_pipeline};
use parser::workflow::{self, parse_workflow, ActionInfo, Job, WorkflowDefinition}; use wrkflw_parser::workflow::{self, parse_workflow, ActionInfo, Job, WorkflowDefinition};
use runtime::container::ContainerRuntime; use wrkflw_runtime::container::ContainerRuntime;
use runtime::emulation; use wrkflw_runtime::emulation;
#[allow(unused_variables, unused_assignments)] #[allow(unused_variables, unused_assignments)]
/// Execute a GitHub Actions workflow file locally /// Execute a GitHub Actions workflow file locally
@@ -27,8 +27,8 @@ pub async fn execute_workflow(
workflow_path: &Path, workflow_path: &Path,
config: ExecutionConfig, config: ExecutionConfig,
) -> Result<ExecutionResult, ExecutionError> { ) -> Result<ExecutionResult, ExecutionError> {
logging::info(&format!("Executing workflow: {}", workflow_path.display())); wrkflw_logging::info(&format!("Executing workflow: {}", workflow_path.display()));
logging::info(&format!("Runtime: {:?}", config.runtime_type)); wrkflw_logging::info(&format!("Runtime: {:?}", config.runtime_type));
// Determine if this is a GitLab CI/CD pipeline or GitHub Actions workflow // Determine if this is a GitLab CI/CD pipeline or GitHub Actions workflow
let is_gitlab = is_gitlab_pipeline(workflow_path); let is_gitlab = is_gitlab_pipeline(workflow_path);
@@ -150,7 +150,7 @@ async fn execute_github_workflow(
// If there were failures, add detailed failure information to the result // If there were failures, add detailed failure information to the result
if has_failures { if has_failures {
logging::error(&format!("Workflow execution failed:{}", failure_details)); wrkflw_logging::error(&format!("Workflow execution failed:{}", failure_details));
} }
Ok(ExecutionResult { Ok(ExecutionResult {
@@ -168,7 +168,7 @@ async fn execute_gitlab_pipeline(
pipeline_path: &Path, pipeline_path: &Path,
config: ExecutionConfig, config: ExecutionConfig,
) -> Result<ExecutionResult, ExecutionError> { ) -> Result<ExecutionResult, ExecutionError> {
logging::info("Executing GitLab CI/CD pipeline"); wrkflw_logging::info("Executing GitLab CI/CD pipeline");
// 1. Parse the GitLab pipeline file // 1. Parse the GitLab pipeline file
let pipeline = parse_pipeline(pipeline_path) let pipeline = parse_pipeline(pipeline_path)
@@ -244,7 +244,7 @@ async fn execute_gitlab_pipeline(
// If there were failures, add detailed failure information to the result // If there were failures, add detailed failure information to the result
if has_failures { if has_failures {
logging::error(&format!("Pipeline execution failed:{}", failure_details)); wrkflw_logging::error(&format!("Pipeline execution failed:{}", failure_details));
} }
Ok(ExecutionResult { Ok(ExecutionResult {
@@ -369,7 +369,7 @@ fn initialize_runtime(
match docker::DockerRuntime::new_with_config(preserve_containers_on_failure) { match docker::DockerRuntime::new_with_config(preserve_containers_on_failure) {
Ok(docker_runtime) => Ok(Box::new(docker_runtime)), Ok(docker_runtime) => Ok(Box::new(docker_runtime)),
Err(e) => { Err(e) => {
logging::error(&format!( wrkflw_logging::error(&format!(
"Failed to initialize Docker runtime: {}, falling back to emulation mode", "Failed to initialize Docker runtime: {}, falling back to emulation mode",
e e
)); ));
@@ -377,7 +377,7 @@ fn initialize_runtime(
} }
} }
} else { } else {
logging::error("Docker not available, falling back to emulation mode"); wrkflw_logging::error("Docker not available, falling back to emulation mode");
Ok(Box::new(emulation::EmulationRuntime::new())) Ok(Box::new(emulation::EmulationRuntime::new()))
} }
} }
@@ -387,7 +387,7 @@ fn initialize_runtime(
match podman::PodmanRuntime::new_with_config(preserve_containers_on_failure) { match podman::PodmanRuntime::new_with_config(preserve_containers_on_failure) {
Ok(podman_runtime) => Ok(Box::new(podman_runtime)), Ok(podman_runtime) => Ok(Box::new(podman_runtime)),
Err(e) => { Err(e) => {
logging::error(&format!( wrkflw_logging::error(&format!(
"Failed to initialize Podman runtime: {}, falling back to emulation mode", "Failed to initialize Podman runtime: {}, falling back to emulation mode",
e e
)); ));
@@ -395,7 +395,7 @@ fn initialize_runtime(
} }
} }
} else { } else {
logging::error("Podman not available, falling back to emulation mode"); wrkflw_logging::error("Podman not available, falling back to emulation mode");
Ok(Box::new(emulation::EmulationRuntime::new())) Ok(Box::new(emulation::EmulationRuntime::new()))
} }
} }
@@ -577,7 +577,7 @@ async fn execute_job_with_matrix(
if let Some(if_condition) = &job.if_condition { if let Some(if_condition) = &job.if_condition {
let should_run = evaluate_job_condition(if_condition, env_context, workflow); let should_run = evaluate_job_condition(if_condition, env_context, workflow);
if !should_run { if !should_run {
logging::info(&format!( wrkflw_logging::info(&format!(
"⏭️ Skipping job '{}' due to condition: {}", "⏭️ Skipping job '{}' due to condition: {}",
job_name, if_condition job_name, if_condition
)); ));
@@ -594,11 +594,11 @@ async fn execute_job_with_matrix(
// Check if this is a matrix job // Check if this is a matrix job
if let Some(matrix_config) = &job.matrix { if let Some(matrix_config) = &job.matrix {
// Expand the matrix into combinations // Expand the matrix into combinations
let combinations = matrix::expand_matrix(matrix_config) let combinations = wrkflw_matrix::expand_matrix(matrix_config)
.map_err(|e| ExecutionError::Execution(format!("Failed to expand matrix: {}", e)))?; .map_err(|e| ExecutionError::Execution(format!("Failed to expand matrix: {}", e)))?;
if combinations.is_empty() { if combinations.is_empty() {
logging::info(&format!( wrkflw_logging::info(&format!(
"Matrix job '{}' has no valid combinations", "Matrix job '{}' has no valid combinations",
job_name job_name
)); ));
@@ -606,7 +606,7 @@ async fn execute_job_with_matrix(
return Ok(Vec::new()); return Ok(Vec::new());
} }
logging::info(&format!( wrkflw_logging::info(&format!(
"Matrix job '{}' expanded to {} combinations", "Matrix job '{}' expanded to {} combinations",
job_name, job_name,
combinations.len() combinations.len()
@@ -674,13 +674,13 @@ async fn execute_job(ctx: JobExecutionContext<'_>) -> Result<JobResult, Executio
})?; })?;
// Copy project files to the job workspace directory // Copy project files to the job workspace directory
logging::info(&format!( wrkflw_logging::info(&format!(
"Copying project files to job workspace: {}", "Copying project files to job workspace: {}",
job_dir.path().display() job_dir.path().display()
)); ));
copy_directory_contents(&current_dir, job_dir.path())?; copy_directory_contents(&current_dir, job_dir.path())?;
logging::info(&format!("Executing job: {}", ctx.job_name)); wrkflw_logging::info(&format!("Executing job: {}", ctx.job_name));
let mut job_success = true; let mut job_success = true;
@@ -780,7 +780,8 @@ async fn execute_matrix_combinations(
if ctx.fail_fast && any_failed { if ctx.fail_fast && any_failed {
// Add skipped results for remaining combinations // Add skipped results for remaining combinations
for combination in chunk { for combination in chunk {
let combination_name = matrix::format_combination_name(ctx.job_name, combination); let combination_name =
wrkflw_matrix::format_combination_name(ctx.job_name, combination);
results.push(JobResult { results.push(JobResult {
name: combination_name, name: combination_name,
status: JobStatus::Skipped, status: JobStatus::Skipped,
@@ -818,7 +819,7 @@ async fn execute_matrix_combinations(
Err(e) => { Err(e) => {
// On error, mark as failed and continue if not fail-fast // On error, mark as failed and continue if not fail-fast
any_failed = true; any_failed = true;
logging::error(&format!("Matrix job failed: {}", e)); wrkflw_logging::error(&format!("Matrix job failed: {}", e));
if ctx.fail_fast { if ctx.fail_fast {
return Err(e); return Err(e);
@@ -842,9 +843,9 @@ async fn execute_matrix_job(
verbose: bool, verbose: bool,
) -> Result<JobResult, ExecutionError> { ) -> Result<JobResult, ExecutionError> {
// Create the matrix-specific job name // Create the matrix-specific job name
let matrix_job_name = matrix::format_combination_name(job_name, combination); let matrix_job_name = wrkflw_matrix::format_combination_name(job_name, combination);
logging::info(&format!("Executing matrix job: {}", matrix_job_name)); wrkflw_logging::info(&format!("Executing matrix job: {}", matrix_job_name));
// Clone the environment and add matrix-specific values // Clone the environment and add matrix-specific values
let mut job_env = base_env_context.clone(); let mut job_env = base_env_context.clone();
@@ -870,14 +871,14 @@ async fn execute_matrix_job(
})?; })?;
// Copy project files to the job workspace directory // Copy project files to the job workspace directory
logging::info(&format!( wrkflw_logging::info(&format!(
"Copying project files to job workspace: {}", "Copying project files to job workspace: {}",
job_dir.path().display() job_dir.path().display()
)); ));
copy_directory_contents(&current_dir, job_dir.path())?; copy_directory_contents(&current_dir, job_dir.path())?;
let job_success = if job_template.steps.is_empty() { let job_success = if job_template.steps.is_empty() {
logging::warning(&format!("Job '{}' has no steps", matrix_job_name)); wrkflw_logging::warning(&format!("Job '{}' has no steps", matrix_job_name));
true true
} else { } else {
// Execute each step // Execute each step
@@ -971,7 +972,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
.unwrap_or_else(|| format!("Step {}", ctx.step_idx + 1)); .unwrap_or_else(|| format!("Step {}", ctx.step_idx + 1));
if ctx.verbose { if ctx.verbose {
logging::info(&format!(" Executing step: {}", step_name)); wrkflw_logging::info(&format!(" Executing step: {}", step_name));
} }
// Prepare step environment // Prepare step environment
@@ -1073,7 +1074,9 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
// Special handling for Rust actions // Special handling for Rust actions
if uses.starts_with("actions-rs/") { if uses.starts_with("actions-rs/") {
logging::info("🔄 Detected Rust action - using system Rust installation"); wrkflw_logging::info(
"🔄 Detected Rust action - using system Rust installation",
);
// For toolchain action, verify Rust is installed // For toolchain action, verify Rust is installed
if uses.starts_with("actions-rs/toolchain@") { if uses.starts_with("actions-rs/toolchain@") {
@@ -1083,7 +1086,10 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
.map(|output| String::from_utf8_lossy(&output.stdout).to_string()) .map(|output| String::from_utf8_lossy(&output.stdout).to_string())
.unwrap_or_else(|_| "not found".to_string()); .unwrap_or_else(|_| "not found".to_string());
logging::info(&format!("🔄 Using system Rust: {}", rustc_version.trim())); wrkflw_logging::info(&format!(
"🔄 Using system Rust: {}",
rustc_version.trim()
));
// Return success since we're using system Rust // Return success since we're using system Rust
return Ok(StepResult { return Ok(StepResult {
@@ -1101,7 +1107,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
.map(|output| String::from_utf8_lossy(&output.stdout).to_string()) .map(|output| String::from_utf8_lossy(&output.stdout).to_string())
.unwrap_or_else(|_| "not found".to_string()); .unwrap_or_else(|_| "not found".to_string());
logging::info(&format!( wrkflw_logging::info(&format!(
"🔄 Using system Rust/Cargo: {}", "🔄 Using system Rust/Cargo: {}",
cargo_version.trim() cargo_version.trim()
)); ));
@@ -1109,7 +1115,10 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
// Get the command from the 'with' parameters // Get the command from the 'with' parameters
if let Some(with_params) = &ctx.step.with { if let Some(with_params) = &ctx.step.with {
if let Some(command) = with_params.get("command") { if let Some(command) = with_params.get("command") {
logging::info(&format!("🔄 Found command parameter: {}", command)); wrkflw_logging::info(&format!(
"🔄 Found command parameter: {}",
command
));
// Build the actual command // Build the actual command
let mut real_command = format!("cargo {}", command); let mut real_command = format!("cargo {}", command);
@@ -1119,7 +1128,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
if !args.is_empty() { if !args.is_empty() {
// Resolve GitHub-style variables in args // Resolve GitHub-style variables in args
let resolved_args = if args.contains("${{") { let resolved_args = if args.contains("${{") {
logging::info(&format!( wrkflw_logging::info(&format!(
"🔄 Resolving workflow variables in: {}", "🔄 Resolving workflow variables in: {}",
args args
)); ));
@@ -1133,7 +1142,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
let re_pattern = let re_pattern =
regex::Regex::new(r"\$\{\{\s*([^}]+)\s*\}\}") regex::Regex::new(r"\$\{\{\s*([^}]+)\s*\}\}")
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
logging::error( wrkflw_logging::error(
"Failed to create regex pattern", "Failed to create regex pattern",
); );
regex::Regex::new(r"\$\{\{.*?\}\}").unwrap() regex::Regex::new(r"\$\{\{.*?\}\}").unwrap()
@@ -1141,7 +1150,10 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
let resolved = let resolved =
re_pattern.replace_all(&resolved, "").to_string(); re_pattern.replace_all(&resolved, "").to_string();
logging::info(&format!("🔄 Resolved to: {}", resolved)); wrkflw_logging::info(&format!(
"🔄 Resolved to: {}",
resolved
));
resolved.trim().to_string() resolved.trim().to_string()
} else { } else {
@@ -1157,7 +1169,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
} }
} }
logging::info(&format!( wrkflw_logging::info(&format!(
"🔄 Running actual command: {}", "🔄 Running actual command: {}",
real_command real_command
)); ));
@@ -1239,13 +1251,13 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
.cloned() .cloned()
.unwrap_or_else(|| "not set".to_string()); .unwrap_or_else(|| "not set".to_string());
logging::debug(&format!( wrkflw_logging::debug(&format!(
"WRKFLW_HIDE_ACTION_MESSAGES value: {}", "WRKFLW_HIDE_ACTION_MESSAGES value: {}",
hide_action_value hide_action_value
)); ));
let hide_messages = hide_action_value == "true"; let hide_messages = hide_action_value == "true";
logging::debug(&format!("Should hide messages: {}", hide_messages)); wrkflw_logging::debug(&format!("Should hide messages: {}", hide_messages));
// Only log a message to the console if we're showing action messages // Only log a message to the console if we're showing action messages
if !hide_messages { if !hide_messages {
@@ -1262,7 +1274,10 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
// Common GitHub action pattern: has a 'command' parameter // Common GitHub action pattern: has a 'command' parameter
if let Some(cmd) = with_params.get("command") { if let Some(cmd) = with_params.get("command") {
if ctx.verbose { if ctx.verbose {
logging::info(&format!("🔄 Found command parameter: {}", cmd)); wrkflw_logging::info(&format!(
"🔄 Found command parameter: {}",
cmd
));
} }
// Convert to real command based on action type patterns // Convert to real command based on action type patterns
@@ -1302,7 +1317,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
if !args.is_empty() { if !args.is_empty() {
// Resolve GitHub-style variables in args // Resolve GitHub-style variables in args
let resolved_args = if args.contains("${{") { let resolved_args = if args.contains("${{") {
logging::info(&format!( wrkflw_logging::info(&format!(
"🔄 Resolving workflow variables in: {}", "🔄 Resolving workflow variables in: {}",
args args
)); ));
@@ -1315,7 +1330,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
let re_pattern = let re_pattern =
regex::Regex::new(r"\$\{\{\s*([^}]+)\s*\}\}") regex::Regex::new(r"\$\{\{\s*([^}]+)\s*\}\}")
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
logging::error( wrkflw_logging::error(
"Failed to create regex pattern", "Failed to create regex pattern",
); );
regex::Regex::new(r"\$\{\{.*?\}\}").unwrap() regex::Regex::new(r"\$\{\{.*?\}\}").unwrap()
@@ -1323,7 +1338,10 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
let resolved = let resolved =
re_pattern.replace_all(&resolved, "").to_string(); re_pattern.replace_all(&resolved, "").to_string();
logging::info(&format!("🔄 Resolved to: {}", resolved)); wrkflw_logging::info(&format!(
"🔄 Resolved to: {}",
resolved
));
resolved.trim().to_string() resolved.trim().to_string()
} else { } else {
@@ -1342,7 +1360,10 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
if should_run_real_command && !real_command_parts.is_empty() { if should_run_real_command && !real_command_parts.is_empty() {
// Build a final command string // Build a final command string
let command_str = real_command_parts.join(" "); let command_str = real_command_parts.join(" ");
logging::info(&format!("🔄 Running actual command: {}", command_str)); wrkflw_logging::info(&format!(
"🔄 Running actual command: {}",
command_str
));
// Replace the emulated command with a shell command to execute our command // Replace the emulated command with a shell command to execute our command
cmd.clear(); cmd.clear();
@@ -1737,7 +1758,7 @@ async fn prepare_runner_image(
) -> Result<(), ExecutionError> { ) -> Result<(), ExecutionError> {
// Try to pull the image first // Try to pull the image first
if let Err(e) = runtime.pull_image(image).await { if let Err(e) = runtime.pull_image(image).await {
logging::warning(&format!("Failed to pull image {}: {}", image, e)); wrkflw_logging::warning(&format!("Failed to pull image {}: {}", image, e));
} }
// Check if this is a language-specific runner // Check if this is a language-specific runner
@@ -1750,7 +1771,7 @@ async fn prepare_runner_image(
.map_err(|e| ExecutionError::Runtime(e.to_string())) .map_err(|e| ExecutionError::Runtime(e.to_string()))
{ {
if verbose { if verbose {
logging::info(&format!("Using customized image: {}", custom_image)); wrkflw_logging::info(&format!("Using customized image: {}", custom_image));
} }
return Ok(()); return Ok(());
} }
@@ -2028,7 +2049,7 @@ fn evaluate_job_condition(
env_context: &HashMap<String, String>, env_context: &HashMap<String, String>,
workflow: &WorkflowDefinition, workflow: &WorkflowDefinition,
) -> bool { ) -> bool {
logging::debug(&format!("Evaluating condition: {}", condition)); wrkflw_logging::debug(&format!("Evaluating condition: {}", condition));
// For now, implement basic pattern matching for common conditions // For now, implement basic pattern matching for common conditions
// TODO: Implement a full GitHub Actions expression evaluator // TODO: Implement a full GitHub Actions expression evaluator
@@ -2051,14 +2072,14 @@ fn evaluate_job_condition(
if condition.contains("needs.") && condition.contains(".outputs.") { if condition.contains("needs.") && condition.contains(".outputs.") {
// For now, simulate that outputs are available but empty // For now, simulate that outputs are available but empty
// This means conditions like needs.changes.outputs.source-code == 'true' will be false // This means conditions like needs.changes.outputs.source-code == 'true' will be false
logging::debug( wrkflw_logging::debug(
"Evaluating needs.outputs condition - defaulting to false for local execution", "Evaluating needs.outputs condition - defaulting to false for local execution",
); );
return false; return false;
} }
// Default to true for unknown conditions to avoid breaking workflows // Default to true for unknown conditions to avoid breaking workflows
logging::warning(&format!( wrkflw_logging::warning(&format!(
"Unknown condition pattern: '{}' - defaulting to true", "Unknown condition pattern: '{}' - defaulting to true",
condition condition
)); ));

View File

@@ -1,8 +1,8 @@
use chrono::Utc; use chrono::Utc;
use matrix::MatrixCombination;
use parser::workflow::WorkflowDefinition;
use serde_yaml::Value; use serde_yaml::Value;
use std::{collections::HashMap, fs, io, path::Path}; use std::{collections::HashMap, fs, io, path::Path};
use wrkflw_matrix::MatrixCombination;
use wrkflw_parser::workflow::WorkflowDefinition;
pub fn setup_github_environment_files(workspace_dir: &Path) -> io::Result<()> { pub fn setup_github_environment_files(workspace_dir: &Path) -> io::Result<()> {
// Create necessary directories // Create necessary directories

View File

@@ -1,15 +1,15 @@
use async_trait::async_trait; use async_trait::async_trait;
use logging;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use runtime::container::{ContainerError, ContainerOutput, ContainerRuntime};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::process::Stdio; use std::process::Stdio;
use std::sync::Mutex; use std::sync::Mutex;
use tempfile; use tempfile;
use tokio::process::Command; use tokio::process::Command;
use utils; use wrkflw_logging;
use utils::fd; use wrkflw_runtime::container::{ContainerError, ContainerOutput, ContainerRuntime};
use wrkflw_utils;
use wrkflw_utils::fd;
static RUNNING_CONTAINERS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new())); static RUNNING_CONTAINERS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
// Map to track customized images for a job // Map to track customized images for a job
@@ -46,7 +46,7 @@ impl PodmanRuntime {
match CUSTOMIZED_IMAGES.lock() { match CUSTOMIZED_IMAGES.lock() {
Ok(images) => images.get(&key).cloned(), Ok(images) => images.get(&key).cloned(),
Err(e) => { Err(e) => {
logging::error(&format!("Failed to acquire lock: {}", e)); wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
None None
} }
} }
@@ -58,7 +58,7 @@ impl PodmanRuntime {
if let Err(e) = CUSTOMIZED_IMAGES.lock().map(|mut images| { if let Err(e) = CUSTOMIZED_IMAGES.lock().map(|mut images| {
images.insert(key, new_image.to_string()); images.insert(key, new_image.to_string());
}) { }) {
logging::error(&format!("Failed to acquire lock: {}", e)); wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
} }
} }
@@ -68,7 +68,7 @@ impl PodmanRuntime {
let image_keys = match CUSTOMIZED_IMAGES.lock() { let image_keys = match CUSTOMIZED_IMAGES.lock() {
Ok(keys) => keys, Ok(keys) => keys,
Err(e) => { Err(e) => {
logging::error(&format!("Failed to acquire lock: {}", e)); wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
return None; return None;
} }
}; };
@@ -103,7 +103,7 @@ impl PodmanRuntime {
match CUSTOMIZED_IMAGES.lock() { match CUSTOMIZED_IMAGES.lock() {
Ok(images) => images.get(&key).cloned(), Ok(images) => images.get(&key).cloned(),
Err(e) => { Err(e) => {
logging::error(&format!("Failed to acquire lock: {}", e)); wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
None None
} }
} }
@@ -130,7 +130,7 @@ impl PodmanRuntime {
if let Err(e) = CUSTOMIZED_IMAGES.lock().map(|mut images| { if let Err(e) = CUSTOMIZED_IMAGES.lock().map(|mut images| {
images.insert(key, new_image.to_string()); images.insert(key, new_image.to_string());
}) { }) {
logging::error(&format!("Failed to acquire lock: {}", e)); wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
} }
} }
@@ -151,7 +151,7 @@ impl PodmanRuntime {
} }
cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
logging::debug(&format!( wrkflw_logging::debug(&format!(
"Running Podman command: podman {}", "Running Podman command: podman {}",
args.join(" ") args.join(" ")
)); ));
@@ -192,7 +192,7 @@ impl PodmanRuntime {
match result { match result {
Ok(output) => output, Ok(output) => output,
Err(_) => { Err(_) => {
logging::error("Podman operation timed out after 360 seconds"); wrkflw_logging::error("Podman operation timed out after 360 seconds");
Err(ContainerError::ContainerExecution( Err(ContainerError::ContainerExecution(
"Operation timed out".to_string(), "Operation timed out".to_string(),
)) ))
@@ -244,7 +244,7 @@ pub fn is_available() -> bool {
} }
} }
Err(_) => { Err(_) => {
logging::debug("Podman CLI is not available"); wrkflw_logging::debug("Podman CLI is not available");
return false; return false;
} }
} }
@@ -257,7 +257,7 @@ pub fn is_available() -> bool {
{ {
Ok(rt) => rt, Ok(rt) => rt,
Err(e) => { Err(e) => {
logging::error(&format!( wrkflw_logging::error(&format!(
"Failed to create runtime for Podman availability check: {}", "Failed to create runtime for Podman availability check: {}",
e e
)); ));
@@ -278,16 +278,16 @@ pub fn is_available() -> bool {
if output.status.success() { if output.status.success() {
true true
} else { } else {
logging::debug("Podman info command failed"); wrkflw_logging::debug("Podman info command failed");
false false
} }
} }
Ok(Err(e)) => { Ok(Err(e)) => {
logging::debug(&format!("Podman info command error: {}", e)); wrkflw_logging::debug(&format!("Podman info command error: {}", e));
false false
} }
Err(_) => { Err(_) => {
logging::debug("Podman info command timed out after 1 second"); wrkflw_logging::debug("Podman info command timed out after 1 second");
false false
} }
} }
@@ -296,7 +296,7 @@ pub fn is_available() -> bool {
{ {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::debug("Podman availability check timed out"); wrkflw_logging::debug("Podman availability check timed out");
false false
} }
} }
@@ -304,7 +304,9 @@ pub fn is_available() -> bool {
}) { }) {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::debug("Failed to redirect stderr when checking Podman availability"); wrkflw_logging::debug(
"Failed to redirect stderr when checking Podman availability",
);
false false
} }
} }
@@ -318,7 +320,7 @@ pub fn is_available() -> bool {
return match handle.join() { return match handle.join() {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::warning("Podman availability check thread panicked"); wrkflw_logging::warning("Podman availability check thread panicked");
false false
} }
}; };
@@ -326,7 +328,9 @@ pub fn is_available() -> bool {
std::thread::sleep(std::time::Duration::from_millis(50)); std::thread::sleep(std::time::Duration::from_millis(50));
} }
logging::warning("Podman availability check timed out, assuming Podman is not available"); wrkflw_logging::warning(
"Podman availability check timed out, assuming Podman is not available",
);
false false
} }
@@ -352,12 +356,12 @@ pub async fn cleanup_resources() {
match tokio::time::timeout(cleanup_timeout, cleanup_containers()).await { match tokio::time::timeout(cleanup_timeout, cleanup_containers()).await {
Ok(result) => { Ok(result) => {
if let Err(e) = result { if let Err(e) = result {
logging::error(&format!("Error during container cleanup: {}", e)); wrkflw_logging::error(&format!("Error during container cleanup: {}", e));
} }
} }
Err(_) => { Err(_) => wrkflw_logging::warning(
logging::warning("Podman cleanup timed out, some resources may not have been removed") "Podman cleanup timed out, some resources may not have been removed",
} ),
} }
} }
@@ -369,7 +373,7 @@ pub async fn cleanup_containers() -> Result<(), String> {
match RUNNING_CONTAINERS.try_lock() { match RUNNING_CONTAINERS.try_lock() {
Ok(containers) => containers.clone(), Ok(containers) => containers.clone(),
Err(_) => { Err(_) => {
logging::error("Could not acquire container lock for cleanup"); wrkflw_logging::error("Could not acquire container lock for cleanup");
vec![] vec![]
} }
} }
@@ -378,7 +382,7 @@ pub async fn cleanup_containers() -> Result<(), String> {
{ {
Ok(containers) => containers, Ok(containers) => containers,
Err(_) => { Err(_) => {
logging::error("Timeout while trying to get containers for cleanup"); wrkflw_logging::error("Timeout while trying to get containers for cleanup");
vec![] vec![]
} }
}; };
@@ -387,7 +391,7 @@ pub async fn cleanup_containers() -> Result<(), String> {
return Ok(()); return Ok(());
} }
logging::info(&format!( wrkflw_logging::info(&format!(
"Cleaning up {} containers", "Cleaning up {} containers",
containers_to_cleanup.len() containers_to_cleanup.len()
)); ));
@@ -408,15 +412,18 @@ pub async fn cleanup_containers() -> Result<(), String> {
match stop_result { match stop_result {
Ok(Ok(output)) => { Ok(Ok(output)) => {
if output.status.success() { if output.status.success() {
logging::debug(&format!("Stopped container: {}", container_id)); wrkflw_logging::debug(&format!("Stopped container: {}", container_id));
} else { } else {
logging::warning(&format!("Error stopping container {}", container_id)); wrkflw_logging::warning(&format!("Error stopping container {}", container_id));
} }
} }
Ok(Err(e)) => { Ok(Err(e)) => wrkflw_logging::warning(&format!(
logging::warning(&format!("Error stopping container {}: {}", container_id, e)) "Error stopping container {}: {}",
container_id, e
)),
Err(_) => {
wrkflw_logging::warning(&format!("Timeout stopping container: {}", container_id))
} }
Err(_) => logging::warning(&format!("Timeout stopping container: {}", container_id)),
} }
// Then try to remove it // Then try to remove it
@@ -433,15 +440,18 @@ pub async fn cleanup_containers() -> Result<(), String> {
match remove_result { match remove_result {
Ok(Ok(output)) => { Ok(Ok(output)) => {
if output.status.success() { if output.status.success() {
logging::debug(&format!("Removed container: {}", container_id)); wrkflw_logging::debug(&format!("Removed container: {}", container_id));
} else { } else {
logging::warning(&format!("Error removing container {}", container_id)); wrkflw_logging::warning(&format!("Error removing container {}", container_id));
} }
} }
Ok(Err(e)) => { Ok(Err(e)) => wrkflw_logging::warning(&format!(
logging::warning(&format!("Error removing container {}: {}", container_id, e)) "Error removing container {}: {}",
container_id, e
)),
Err(_) => {
wrkflw_logging::warning(&format!("Timeout removing container: {}", container_id))
} }
Err(_) => logging::warning(&format!("Timeout removing container: {}", container_id)),
} }
// Always untrack the container whether or not we succeeded to avoid future cleanup attempts // Always untrack the container whether or not we succeeded to avoid future cleanup attempts
@@ -462,7 +472,7 @@ impl ContainerRuntime for PodmanRuntime {
volumes: &[(&Path, &Path)], volumes: &[(&Path, &Path)],
) -> Result<ContainerOutput, ContainerError> { ) -> Result<ContainerOutput, ContainerError> {
// Print detailed debugging info // Print detailed debugging info
logging::info(&format!("Podman: Running container with image: {}", image)); wrkflw_logging::info(&format!("Podman: Running container with image: {}", image));
let timeout_duration = std::time::Duration::from_secs(360); // 6 minutes timeout let timeout_duration = std::time::Duration::from_secs(360); // 6 minutes timeout
@@ -475,7 +485,7 @@ impl ContainerRuntime for PodmanRuntime {
{ {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::error("Podman operation timed out after 360 seconds"); wrkflw_logging::error("Podman operation timed out after 360 seconds");
Err(ContainerError::ContainerExecution( Err(ContainerError::ContainerExecution(
"Operation timed out".to_string(), "Operation timed out".to_string(),
)) ))
@@ -490,7 +500,7 @@ impl ContainerRuntime for PodmanRuntime {
match tokio::time::timeout(timeout_duration, self.pull_image_inner(image)).await { match tokio::time::timeout(timeout_duration, self.pull_image_inner(image)).await {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::warning(&format!( wrkflw_logging::warning(&format!(
"Pull of image {} timed out, continuing with existing image", "Pull of image {} timed out, continuing with existing image",
image image
)); ));
@@ -508,7 +518,7 @@ impl ContainerRuntime for PodmanRuntime {
{ {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::error(&format!( wrkflw_logging::error(&format!(
"Building image {} timed out after 120 seconds", "Building image {} timed out after 120 seconds",
tag tag
)); ));
@@ -664,9 +674,9 @@ impl PodmanRuntime {
working_dir: &Path, working_dir: &Path,
volumes: &[(&Path, &Path)], volumes: &[(&Path, &Path)],
) -> Result<ContainerOutput, ContainerError> { ) -> Result<ContainerOutput, ContainerError> {
logging::debug(&format!("Running command in Podman: {:?}", cmd)); wrkflw_logging::debug(&format!("Running command in Podman: {:?}", cmd));
logging::debug(&format!("Environment: {:?}", env_vars)); wrkflw_logging::debug(&format!("Environment: {:?}", env_vars));
logging::debug(&format!("Working directory: {}", working_dir.display())); wrkflw_logging::debug(&format!("Working directory: {}", working_dir.display()));
// Generate a unique container name // Generate a unique container name
let container_name = format!("wrkflw-{}", uuid::Uuid::new_v4()); let container_name = format!("wrkflw-{}", uuid::Uuid::new_v4());
@@ -742,13 +752,13 @@ impl PodmanRuntime {
match cleanup_result { match cleanup_result {
Ok(Ok(cleanup_output)) => { Ok(Ok(cleanup_output)) => {
if !cleanup_output.status.success() { if !cleanup_output.status.success() {
logging::debug(&format!( wrkflw_logging::debug(&format!(
"Failed to remove successful container {}", "Failed to remove successful container {}",
container_name container_name
)); ));
} }
} }
_ => logging::debug(&format!( _ => wrkflw_logging::debug(&format!(
"Timeout removing successful container {}", "Timeout removing successful container {}",
container_name container_name
)), )),
@@ -760,7 +770,7 @@ impl PodmanRuntime {
// Failed container // Failed container
if self.preserve_containers_on_failure { if self.preserve_containers_on_failure {
// Failed and we want to preserve - don't clean up but untrack from auto-cleanup // Failed and we want to preserve - don't clean up but untrack from auto-cleanup
logging::info(&format!( wrkflw_logging::info(&format!(
"Preserving failed container {} for debugging (exit code: {}). Use 'podman exec -it {} bash' to inspect.", "Preserving failed container {} for debugging (exit code: {}). Use 'podman exec -it {} bash' to inspect.",
container_name, output.exit_code, container_name container_name, output.exit_code, container_name
)); ));
@@ -789,11 +799,11 @@ impl PodmanRuntime {
.await; .await;
match cleanup_result { match cleanup_result {
Ok(Ok(_)) => logging::debug(&format!( Ok(Ok(_)) => wrkflw_logging::debug(&format!(
"Cleaned up failed execution container {}", "Cleaned up failed execution container {}",
container_name container_name
)), )),
_ => logging::debug(&format!( _ => wrkflw_logging::debug(&format!(
"Failed to clean up execution failure container {}", "Failed to clean up execution failure container {}",
container_name container_name
)), )),
@@ -806,17 +816,17 @@ impl PodmanRuntime {
match &result { match &result {
Ok(output) => { Ok(output) => {
if output.exit_code != 0 { if output.exit_code != 0 {
logging::info(&format!( wrkflw_logging::info(&format!(
"Podman command failed with exit code: {}", "Podman command failed with exit code: {}",
output.exit_code output.exit_code
)); ));
logging::debug(&format!("Failed command: {:?}", cmd)); wrkflw_logging::debug(&format!("Failed command: {:?}", cmd));
logging::debug(&format!("Working directory: {}", working_dir.display())); wrkflw_logging::debug(&format!("Working directory: {}", working_dir.display()));
logging::debug(&format!("STDERR: {}", output.stderr)); wrkflw_logging::debug(&format!("STDERR: {}", output.stderr));
} }
} }
Err(e) => { Err(e) => {
logging::error(&format!("Podman execution error: {}", e)); wrkflw_logging::error(&format!("Podman execution error: {}", e));
} }
} }

View File

@@ -1,13 +1,18 @@
[package] [package]
name = "github" name = "wrkflw-github"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
description = "github functionality for wrkflw" description = "GitHub API integration for wrkflw workflow execution engine"
license.workspace = true license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies] [dependencies]
# Add other crate dependencies as needed # Internal crates
models = { path = "../models" } wrkflw-models = { path = "../models", version = "0.5.0" }
# External dependencies from workspace # External dependencies from workspace
serde.workspace = true serde.workspace = true

View File

@@ -1,13 +1,18 @@
[package] [package]
name = "gitlab" name = "wrkflw-gitlab"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
description = "gitlab functionality for wrkflw" description = "GitLab API integration for wrkflw workflow execution engine"
license.workspace = true license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies] [dependencies]
# Internal crates # Internal crates
models = { path = "../models" } wrkflw-models = { path = "../models", version = "0.5.0" }
# External dependencies # External dependencies
lazy_static.workspace = true lazy_static.workspace = true

View File

@@ -1,13 +1,18 @@
[package] [package]
name = "logging" name = "wrkflw-logging"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
description = "logging functionality for wrkflw" description = "Logging functionality for wrkflw workflow execution engine"
license.workspace = true license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies] [dependencies]
# Internal crates # Internal crates
models = { path = "../models" } wrkflw-models = { path = "../models", version = "0.5.0" }
# External dependencies # External dependencies
chrono.workspace = true chrono.workspace = true

View File

@@ -1,13 +1,18 @@
[package] [package]
name = "matrix" name = "wrkflw-matrix"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
description = "matrix functionality for wrkflw" description = "Matrix job parallelization for wrkflw workflow execution engine"
license.workspace = true license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies] [dependencies]
# Internal crates # Internal crates
models = { path = "../models" } wrkflw-models = { path = "../models", version = "0.5.0" }
# External dependencies # External dependencies
indexmap.workspace = true indexmap.workspace = true

View File

@@ -1,9 +1,14 @@
[package] [package]
name = "models" name = "wrkflw-models"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
description = "Data models for wrkflw" description = "Data models and structures for wrkflw workflow execution engine"
license.workspace = true license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies] [dependencies]
serde.workspace = true serde.workspace = true

View File

@@ -1,14 +1,19 @@
[package] [package]
name = "parser" name = "wrkflw-parser"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
description = "Parser functionality for wrkflw" description = "Workflow parsing functionality for wrkflw execution engine"
license.workspace = true license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies] [dependencies]
# Internal crates # Internal crates
models = { path = "../models" } wrkflw-models = { path = "../models", version = "0.5.0" }
matrix = { path = "../matrix" } wrkflw-matrix = { path = "../matrix", version = "0.5.0" }
# External dependencies # External dependencies
jsonschema.workspace = true jsonschema.workspace = true

View File

@@ -1,11 +1,11 @@
use crate::schema::{SchemaType, SchemaValidator}; use crate::schema::{SchemaType, SchemaValidator};
use crate::workflow; use crate::workflow;
use models::gitlab::Pipeline;
use models::ValidationResult;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use thiserror::Error; use thiserror::Error;
use wrkflw_models::gitlab::Pipeline;
use wrkflw_models::ValidationResult;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum GitlabParserError { pub enum GitlabParserError {
@@ -204,8 +204,8 @@ pub fn convert_to_workflow_format(pipeline: &Pipeline) -> workflow::WorkflowDefi
for (i, service) in services.iter().enumerate() { for (i, service) in services.iter().enumerate() {
let service_name = format!("service-{}", i); let service_name = format!("service-{}", i);
let service_image = match service { let service_image = match service {
models::gitlab::Service::Simple(name) => name.clone(), wrkflw_models::gitlab::Service::Simple(name) => name.clone(),
models::gitlab::Service::Detailed { name, .. } => name.clone(), wrkflw_models::gitlab::Service::Detailed { name, .. } => name.clone(),
}; };
let service = workflow::Service { let service = workflow::Service {

View File

@@ -1,8 +1,8 @@
use matrix::MatrixConfig;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use wrkflw_matrix::MatrixConfig;
use super::schema::SchemaValidator; use super::schema::SchemaValidator;

View File

@@ -1,14 +1,19 @@
[package] [package]
name = "runtime" name = "wrkflw-runtime"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
description = "Runtime environment for wrkflw" description = "Runtime execution environment for wrkflw workflow engine"
license.workspace = true license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies] [dependencies]
# Internal crates # Internal crates
models = { path = "../models" } wrkflw-models = { path = "../models", version = "0.5.0" }
logging = { path = "../logging", version = "0.5.0" } wrkflw-logging = { path = "../logging", version = "0.5.0" }
# External dependencies # External dependencies
async-trait.workspace = true async-trait.workspace = true
@@ -18,5 +23,5 @@ serde_yaml.workspace = true
tempfile = "3.9" tempfile = "3.9"
tokio.workspace = true tokio.workspace = true
futures = "0.3" futures = "0.3"
utils = { path = "../utils", version = "0.5.0" } wrkflw-utils = { path = "../utils", version = "0.5.0" }
which = "4.4" which = "4.4"

View File

@@ -1,6 +1,5 @@
use crate::container::{ContainerError, ContainerOutput, ContainerRuntime}; use crate::container::{ContainerError, ContainerOutput, ContainerRuntime};
use async_trait::async_trait; use async_trait::async_trait;
use logging;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
@@ -9,6 +8,7 @@ use std::process::Command;
use std::sync::Mutex; use std::sync::Mutex;
use tempfile::TempDir; use tempfile::TempDir;
use which; use which;
use wrkflw_logging;
// Global collection of resources to clean up // Global collection of resources to clean up
static EMULATION_WORKSPACES: Lazy<Mutex<Vec<PathBuf>>> = Lazy::new(|| Mutex::new(Vec::new())); static EMULATION_WORKSPACES: Lazy<Mutex<Vec<PathBuf>>> = Lazy::new(|| Mutex::new(Vec::new()));
@@ -162,9 +162,9 @@ impl ContainerRuntime for EmulationRuntime {
} }
// Log more detailed debugging information // Log more detailed debugging information
logging::info(&format!("Executing command in container: {}", command_str)); wrkflw_logging::info(&format!("Executing command in container: {}", command_str));
logging::info(&format!("Working directory: {}", working_dir.display())); wrkflw_logging::info(&format!("Working directory: {}", working_dir.display()));
logging::info(&format!("Command length: {}", command.len())); wrkflw_logging::info(&format!("Command length: {}", command.len()));
if command.is_empty() { if command.is_empty() {
return Err(ContainerError::ContainerExecution( return Err(ContainerError::ContainerExecution(
@@ -174,13 +174,13 @@ impl ContainerRuntime for EmulationRuntime {
// Print each command part separately for debugging // Print each command part separately for debugging
for (i, part) in command.iter().enumerate() { for (i, part) in command.iter().enumerate() {
logging::info(&format!("Command part {}: '{}'", i, part)); wrkflw_logging::info(&format!("Command part {}: '{}'", i, part));
} }
// Log environment variables // Log environment variables
logging::info("Environment variables:"); wrkflw_logging::info("Environment variables:");
for (key, value) in env_vars { for (key, value) in env_vars {
logging::info(&format!(" {}={}", key, value)); wrkflw_logging::info(&format!(" {}={}", key, value));
} }
// Find actual working directory - determine if we should use the current directory instead // Find actual working directory - determine if we should use the current directory instead
@@ -197,7 +197,7 @@ impl ContainerRuntime for EmulationRuntime {
// If found, use that as the working directory // If found, use that as the working directory
if let Some(path) = workspace_path { if let Some(path) = workspace_path {
if path.exists() { if path.exists() {
logging::info(&format!( wrkflw_logging::info(&format!(
"Using environment-defined workspace: {}", "Using environment-defined workspace: {}",
path.display() path.display()
)); ));
@@ -206,7 +206,7 @@ impl ContainerRuntime for EmulationRuntime {
// Fallback to current directory // Fallback to current directory
let current_dir = let current_dir =
std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
logging::info(&format!( wrkflw_logging::info(&format!(
"Using current directory: {}", "Using current directory: {}",
current_dir.display() current_dir.display()
)); ));
@@ -215,7 +215,7 @@ impl ContainerRuntime for EmulationRuntime {
} else { } else {
// Fallback to current directory // Fallback to current directory
let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
logging::info(&format!( wrkflw_logging::info(&format!(
"Using current directory: {}", "Using current directory: {}",
current_dir.display() current_dir.display()
)); ));
@@ -225,7 +225,7 @@ impl ContainerRuntime for EmulationRuntime {
working_dir.to_path_buf() working_dir.to_path_buf()
}; };
logging::info(&format!( wrkflw_logging::info(&format!(
"Using actual working directory: {}", "Using actual working directory: {}",
actual_working_dir.display() actual_working_dir.display()
)); ));
@@ -233,8 +233,8 @@ impl ContainerRuntime for EmulationRuntime {
// Check if path contains the command (for shell script execution) // Check if path contains the command (for shell script execution)
let command_path = which::which(command[0]); let command_path = which::which(command[0]);
match &command_path { match &command_path {
Ok(path) => logging::info(&format!("Found command at: {}", path.display())), Ok(path) => wrkflw_logging::info(&format!("Found command at: {}", path.display())),
Err(e) => logging::error(&format!( Err(e) => wrkflw_logging::error(&format!(
"Command not found in PATH: {} - Error: {}", "Command not found in PATH: {} - Error: {}",
command[0], e command[0], e
)), )),
@@ -246,7 +246,7 @@ impl ContainerRuntime for EmulationRuntime {
|| command_str.starts_with("mkdir ") || command_str.starts_with("mkdir ")
|| command_str.starts_with("mv ") || command_str.starts_with("mv ")
{ {
logging::info("Executing as shell command"); wrkflw_logging::info("Executing as shell command");
// Execute as a shell command // Execute as a shell command
let mut cmd = Command::new("sh"); let mut cmd = Command::new("sh");
cmd.arg("-c"); cmd.arg("-c");
@@ -264,7 +264,7 @@ impl ContainerRuntime for EmulationRuntime {
let output = String::from_utf8_lossy(&output_result.stdout).to_string(); let output = String::from_utf8_lossy(&output_result.stdout).to_string();
let error = String::from_utf8_lossy(&output_result.stderr).to_string(); let error = String::from_utf8_lossy(&output_result.stderr).to_string();
logging::debug(&format!( wrkflw_logging::debug(&format!(
"Shell command completed with exit code: {}", "Shell command completed with exit code: {}",
exit_code exit_code
)); ));
@@ -314,7 +314,7 @@ impl ContainerRuntime for EmulationRuntime {
// Always use the current directory for cargo/rust commands rather than the temporary directory // 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(".")); let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
logging::info(&format!( wrkflw_logging::info(&format!(
"Using project directory for Rust command: {}", "Using project directory for Rust command: {}",
current_dir.display() current_dir.display()
)); ));
@@ -326,7 +326,7 @@ impl ContainerRuntime for EmulationRuntime {
if *key == "CARGO_HOME" && value.contains("${CI_PROJECT_DIR}") { if *key == "CARGO_HOME" && value.contains("${CI_PROJECT_DIR}") {
let cargo_home = let cargo_home =
value.replace("${CI_PROJECT_DIR}", &current_dir.to_string_lossy()); value.replace("${CI_PROJECT_DIR}", &current_dir.to_string_lossy());
logging::info(&format!("Setting CARGO_HOME to: {}", cargo_home)); wrkflw_logging::info(&format!("Setting CARGO_HOME to: {}", cargo_home));
cmd.env(key, cargo_home); cmd.env(key, cargo_home);
} else { } else {
cmd.env(key, value); cmd.env(key, value);
@@ -338,7 +338,7 @@ impl ContainerRuntime for EmulationRuntime {
cmd.args(&parts[1..]); cmd.args(&parts[1..]);
} }
logging::debug(&format!( wrkflw_logging::debug(&format!(
"Executing Rust command: {} in {}", "Executing Rust command: {} in {}",
command_str, command_str,
current_dir.display() current_dir.display()
@@ -350,7 +350,7 @@ impl ContainerRuntime for EmulationRuntime {
let output = String::from_utf8_lossy(&output_result.stdout).to_string(); let output = String::from_utf8_lossy(&output_result.stdout).to_string();
let error = String::from_utf8_lossy(&output_result.stderr).to_string(); let error = String::from_utf8_lossy(&output_result.stderr).to_string();
logging::debug(&format!("Command exit code: {}", exit_code)); wrkflw_logging::debug(&format!("Command exit code: {}", exit_code));
if exit_code != 0 { if exit_code != 0 {
let mut error_details = format!( let mut error_details = format!(
@@ -405,7 +405,7 @@ impl ContainerRuntime for EmulationRuntime {
let output = String::from_utf8_lossy(&output_result.stdout).to_string(); let output = String::from_utf8_lossy(&output_result.stdout).to_string();
let error = String::from_utf8_lossy(&output_result.stderr).to_string(); let error = String::from_utf8_lossy(&output_result.stderr).to_string();
logging::debug(&format!("Command completed with exit code: {}", exit_code)); wrkflw_logging::debug(&format!("Command completed with exit code: {}", exit_code));
if exit_code != 0 { if exit_code != 0 {
let mut error_details = format!( let mut error_details = format!(
@@ -443,12 +443,12 @@ impl ContainerRuntime for EmulationRuntime {
} }
async fn pull_image(&self, image: &str) -> Result<(), ContainerError> { async fn pull_image(&self, image: &str) -> Result<(), ContainerError> {
logging::info(&format!("🔄 Emulation: Pretending to pull image {}", image)); wrkflw_logging::info(&format!("🔄 Emulation: Pretending to pull image {}", image));
Ok(()) Ok(())
} }
async fn build_image(&self, dockerfile: &Path, tag: &str) -> Result<(), ContainerError> { async fn build_image(&self, dockerfile: &Path, tag: &str) -> Result<(), ContainerError> {
logging::info(&format!( wrkflw_logging::info(&format!(
"🔄 Emulation: Pretending to build image {} from {}", "🔄 Emulation: Pretending to build image {} from {}",
tag, tag,
dockerfile.display() dockerfile.display()
@@ -543,14 +543,14 @@ pub async fn handle_special_action(action: &str) -> Result<(), ContainerError> {
"latest" "latest"
}; };
logging::info(&format!( wrkflw_logging::info(&format!(
"🔄 Processing action: {} @ {}", "🔄 Processing action: {} @ {}",
action_name, action_version action_name, action_version
)); ));
// Handle specific known actions with special requirements // Handle specific known actions with special requirements
if action.starts_with("cachix/install-nix-action") { if action.starts_with("cachix/install-nix-action") {
logging::info("🔄 Emulating cachix/install-nix-action"); wrkflw_logging::info("🔄 Emulating cachix/install-nix-action");
// In emulation mode, check if nix is installed // In emulation mode, check if nix is installed
let nix_installed = Command::new("which") let nix_installed = Command::new("which")
@@ -560,56 +560,56 @@ pub async fn handle_special_action(action: &str) -> Result<(), ContainerError> {
.unwrap_or(false); .unwrap_or(false);
if !nix_installed { if !nix_installed {
logging::info("🔄 Emulation: Nix is required but not installed."); wrkflw_logging::info("🔄 Emulation: Nix is required but not installed.");
logging::info( wrkflw_logging::info(
"🔄 To use this workflow, please install Nix: https://nixos.org/download.html", "🔄 To use this workflow, please install Nix: https://nixos.org/download.html",
); );
logging::info("🔄 Continuing emulation, but nix commands will fail."); wrkflw_logging::info("🔄 Continuing emulation, but nix commands will fail.");
} else { } else {
logging::info("🔄 Emulation: Using system-installed Nix"); wrkflw_logging::info("🔄 Emulation: Using system-installed Nix");
} }
} else if action.starts_with("actions-rs/cargo@") { } else if action.starts_with("actions-rs/cargo@") {
// For actions-rs/cargo action, ensure Rust is available // For actions-rs/cargo action, ensure Rust is available
logging::info(&format!("🔄 Detected Rust cargo action: {}", action)); wrkflw_logging::info(&format!("🔄 Detected Rust cargo action: {}", action));
// Verify Rust/cargo is installed // Verify Rust/cargo is installed
check_command_available("cargo", "Rust/Cargo", "https://rustup.rs/"); check_command_available("cargo", "Rust/Cargo", "https://rustup.rs/");
} else if action.starts_with("actions-rs/toolchain@") { } else if action.starts_with("actions-rs/toolchain@") {
// For actions-rs/toolchain action, check for Rust installation // For actions-rs/toolchain action, check for Rust installation
logging::info(&format!("🔄 Detected Rust toolchain action: {}", action)); wrkflw_logging::info(&format!("🔄 Detected Rust toolchain action: {}", action));
check_command_available("rustc", "Rust", "https://rustup.rs/"); check_command_available("rustc", "Rust", "https://rustup.rs/");
} else if action.starts_with("actions-rs/fmt@") { } else if action.starts_with("actions-rs/fmt@") {
// For actions-rs/fmt action, check if rustfmt is available // For actions-rs/fmt action, check if rustfmt is available
logging::info(&format!("🔄 Detected Rust formatter action: {}", action)); wrkflw_logging::info(&format!("🔄 Detected Rust formatter action: {}", action));
check_command_available("rustfmt", "rustfmt", "rustup component add rustfmt"); check_command_available("rustfmt", "rustfmt", "rustup component add rustfmt");
} else if action.starts_with("actions/setup-node@") { } else if action.starts_with("actions/setup-node@") {
// Node.js setup action // Node.js setup action
logging::info(&format!("🔄 Detected Node.js setup action: {}", action)); wrkflw_logging::info(&format!("🔄 Detected Node.js setup action: {}", action));
check_command_available("node", "Node.js", "https://nodejs.org/"); check_command_available("node", "Node.js", "https://nodejs.org/");
} else if action.starts_with("actions/setup-python@") { } else if action.starts_with("actions/setup-python@") {
// Python setup action // Python setup action
logging::info(&format!("🔄 Detected Python setup action: {}", action)); wrkflw_logging::info(&format!("🔄 Detected Python setup action: {}", action));
check_command_available("python", "Python", "https://www.python.org/downloads/"); check_command_available("python", "Python", "https://www.python.org/downloads/");
} else if action.starts_with("actions/setup-java@") { } else if action.starts_with("actions/setup-java@") {
// Java setup action // Java setup action
logging::info(&format!("🔄 Detected Java setup action: {}", action)); wrkflw_logging::info(&format!("🔄 Detected Java setup action: {}", action));
check_command_available("java", "Java", "https://adoptium.net/"); check_command_available("java", "Java", "https://adoptium.net/");
} else if action.starts_with("actions/checkout@") { } else if action.starts_with("actions/checkout@") {
// Git checkout action - this is handled implicitly by our workspace setup // Git checkout action - this is handled implicitly by our workspace setup
logging::info("🔄 Detected checkout action - workspace files are already prepared"); wrkflw_logging::info("🔄 Detected checkout action - workspace files are already prepared");
} else if action.starts_with("actions/cache@") { } else if action.starts_with("actions/cache@") {
// Cache action - can't really emulate caching effectively // Cache action - can't really emulate caching effectively
logging::info( wrkflw_logging::info(
"🔄 Detected cache action - caching is not fully supported in emulation mode", "🔄 Detected cache action - caching is not fully supported in emulation mode",
); );
} else { } else {
// Generic action we don't have special handling for // Generic action we don't have special handling for
logging::info(&format!( wrkflw_logging::info(&format!(
"🔄 Action '{}' has no special handling in emulation mode", "🔄 Action '{}' has no special handling in emulation mode",
action_name action_name
)); ));
@@ -628,12 +628,12 @@ fn check_command_available(command: &str, name: &str, install_url: &str) {
.unwrap_or(false); .unwrap_or(false);
if !is_available { if !is_available {
logging::warning(&format!("{} is required but not found on the system", name)); wrkflw_logging::warning(&format!("{} is required but not found on the system", name));
logging::info(&format!( wrkflw_logging::info(&format!(
"To use this action, please install {}: {}", "To use this action, please install {}: {}",
name, install_url name, install_url
)); ));
logging::info(&format!( wrkflw_logging::info(&format!(
"Continuing emulation, but {} commands will fail", "Continuing emulation, but {} commands will fail",
name name
)); ));
@@ -642,7 +642,7 @@ fn check_command_available(command: &str, name: &str, install_url: &str) {
if let Ok(output) = Command::new(command).arg("--version").output() { if let Ok(output) = Command::new(command).arg("--version").output() {
if output.status.success() { if output.status.success() {
let version = String::from_utf8_lossy(&output.stdout); let version = String::from_utf8_lossy(&output.stdout);
logging::info(&format!("🔄 Using system {}: {}", name, version.trim())); wrkflw_logging::info(&format!("🔄 Using system {}: {}", name, version.trim()));
} }
} }
} }
@@ -708,7 +708,7 @@ async fn cleanup_processes() {
}; };
for pid in processes_to_cleanup { for pid in processes_to_cleanup {
logging::info(&format!("Cleaning up emulated process: {}", pid)); wrkflw_logging::info(&format!("Cleaning up emulated process: {}", pid));
#[cfg(unix)] #[cfg(unix)]
{ {
@@ -747,7 +747,7 @@ async fn cleanup_workspaces() {
}; };
for workspace_path in workspaces_to_cleanup { for workspace_path in workspaces_to_cleanup {
logging::info(&format!( wrkflw_logging::info(&format!(
"Cleaning up emulation workspace: {}", "Cleaning up emulation workspace: {}",
workspace_path.display() workspace_path.display()
)); ));
@@ -755,8 +755,8 @@ async fn cleanup_workspaces() {
// Only attempt to remove if it exists // Only attempt to remove if it exists
if workspace_path.exists() { if workspace_path.exists() {
match fs::remove_dir_all(&workspace_path) { match fs::remove_dir_all(&workspace_path) {
Ok(_) => logging::info("Successfully removed workspace directory"), Ok(_) => wrkflw_logging::info("Successfully removed workspace directory"),
Err(e) => logging::error(&format!("Error removing workspace: {}", e)), Err(e) => wrkflw_logging::error(&format!("Error removing workspace: {}", e)),
} }
} }

View File

@@ -1,18 +1,23 @@
[package] [package]
name = "ui" name = "wrkflw-ui"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
description = "user interface functionality for wrkflw" description = "Terminal user interface for wrkflw workflow execution engine"
license.workspace = true license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies] [dependencies]
# Internal crates # Internal crates
models = { path = "../models" } wrkflw-models = { path = "../models", version = "0.5.0" }
evaluator = { path = "../evaluator" } wrkflw-evaluator = { path = "../evaluator", version = "0.5.0" }
executor = { path = "../executor" } wrkflw-executor = { path = "../executor", version = "0.5.0" }
logging = { path = "../logging" } wrkflw-logging = { path = "../logging", version = "0.5.0" }
utils = { path = "../utils" } wrkflw-utils = { path = "../utils", version = "0.5.0" }
github = { path = "../github" } wrkflw-github = { path = "../github", version = "0.5.0" }
# External dependencies # External dependencies
chrono.workspace = true chrono.workspace = true

View File

@@ -11,12 +11,12 @@ use crossterm::{
execute, execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
}; };
use executor::RuntimeType;
use ratatui::{backend::CrosstermBackend, Terminal}; use ratatui::{backend::CrosstermBackend, Terminal};
use std::io::{self, stdout}; use std::io::{self, stdout};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::mpsc; use std::sync::mpsc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use wrkflw_executor::RuntimeType;
pub use state::App; pub use state::App;
@@ -50,7 +50,7 @@ pub async fn run_wrkflw_tui(
if app.validation_mode { if app.validation_mode {
app.logs.push("Starting in validation mode".to_string()); app.logs.push("Starting in validation mode".to_string());
logging::info("Starting in validation mode"); wrkflw_logging::info("Starting in validation mode");
} }
// Load workflows // Load workflows
@@ -108,13 +108,13 @@ pub async fn run_wrkflw_tui(
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(e) => { Err(e) => {
// If the TUI fails to initialize or crashes, fall back to CLI mode // If the TUI fails to initialize or crashes, fall back to CLI mode
logging::error(&format!("Failed to start UI: {}", e)); wrkflw_logging::error(&format!("Failed to start UI: {}", e));
// Only for 'tui' command should we fall back to CLI mode for files // Only for 'tui' command should we fall back to CLI mode for files
// For other commands, return the error // For other commands, return the error
if let Some(path) = path { if let Some(path) = path {
if path.is_file() { if path.is_file() {
logging::error("Falling back to CLI mode..."); wrkflw_logging::error("Falling back to CLI mode...");
crate::handlers::workflow::execute_workflow_cli(path, runtime_type, verbose) crate::handlers::workflow::execute_workflow_cli(path, runtime_type, verbose)
.await .await
} else if path.is_dir() { } else if path.is_dir() {
@@ -273,7 +273,7 @@ fn run_tui_event_loop(
"[{}] DEBUG: Shift+r detected - this should be uppercase R", "[{}] DEBUG: Shift+r detected - this should be uppercase R",
timestamp timestamp
)); ));
logging::info( wrkflw_logging::info(
"Shift+r detected as lowercase - this should be uppercase R", "Shift+r detected as lowercase - this should be uppercase R",
); );
@@ -329,7 +329,7 @@ fn run_tui_event_loop(
"[{}] DEBUG: Reset key 'Shift+R' pressed", "[{}] DEBUG: Reset key 'Shift+R' pressed",
timestamp timestamp
)); ));
logging::info("Reset key 'Shift+R' pressed"); wrkflw_logging::info("Reset key 'Shift+R' pressed");
if !app.running { if !app.running {
// Reset workflow status // Reset workflow status
@@ -367,7 +367,7 @@ fn run_tui_event_loop(
"Workflow '{}' is already running", "Workflow '{}' is already running",
workflow.name workflow.name
)); ));
logging::warning(&format!( wrkflw_logging::warning(&format!(
"Workflow '{}' is already running", "Workflow '{}' is already running",
workflow.name workflow.name
)); ));
@@ -408,7 +408,7 @@ fn run_tui_event_loop(
)); ));
} }
logging::warning(&format!( wrkflw_logging::warning(&format!(
"Cannot trigger workflow in {} state", "Cannot trigger workflow in {} state",
status_text status_text
)); ));
@@ -416,20 +416,22 @@ fn run_tui_event_loop(
} }
} else { } else {
app.logs.push("No workflow selected to trigger".to_string()); app.logs.push("No workflow selected to trigger".to_string());
logging::warning("No workflow selected to trigger"); wrkflw_logging::warning("No workflow selected to trigger");
} }
} else if app.running { } else if app.running {
app.logs.push( app.logs.push(
"Cannot trigger workflow while another operation is in progress" "Cannot trigger workflow while another operation is in progress"
.to_string(), .to_string(),
); );
logging::warning( wrkflw_logging::warning(
"Cannot trigger workflow while another operation is in progress", "Cannot trigger workflow while another operation is in progress",
); );
} else if app.selected_tab != 0 { } else if app.selected_tab != 0 {
app.logs app.logs
.push("Switch to Workflows tab to trigger a workflow".to_string()); .push("Switch to Workflows tab to trigger a workflow".to_string());
logging::warning("Switch to Workflows tab to trigger a workflow"); wrkflw_logging::warning(
"Switch to Workflows tab to trigger a workflow",
);
// For better UX, we could also automatically switch to the Workflows tab here // For better UX, we could also automatically switch to the Workflows tab here
app.switch_tab(0); app.switch_tab(0);
} }

View File

@@ -5,10 +5,10 @@ use crate::models::{
}; };
use chrono::Local; use chrono::Local;
use crossterm::event::KeyCode; use crossterm::event::KeyCode;
use executor::{JobStatus, RuntimeType, StepStatus};
use ratatui::widgets::{ListState, TableState}; use ratatui::widgets::{ListState, TableState};
use std::sync::mpsc; use std::sync::mpsc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use wrkflw_executor::{JobStatus, RuntimeType, StepStatus};
/// Application state /// Application state
pub struct App { pub struct App {
@@ -69,8 +69,10 @@ impl App {
// Use a very short timeout to prevent blocking the UI // Use a very short timeout to prevent blocking the UI
let result = std::thread::scope(|s| { let result = std::thread::scope(|s| {
let handle = s.spawn(|| { let handle = s.spawn(|| {
utils::fd::with_stderr_to_null(executor::docker::is_available) wrkflw_utils::fd::with_stderr_to_null(
.unwrap_or(false) wrkflw_executor::docker::is_available,
)
.unwrap_or(false)
}); });
// Set a short timeout for the thread // Set a short timeout for the thread
@@ -85,7 +87,7 @@ impl App {
} }
// If we reach here, the check took too long // If we reach here, the check took too long
logging::warning( wrkflw_logging::warning(
"Docker availability check timed out, falling back to emulation mode", "Docker availability check timed out, falling back to emulation mode",
); );
false false
@@ -94,7 +96,7 @@ impl App {
}) { }) {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::warning("Docker availability check failed with panic, falling back to emulation mode"); wrkflw_logging::warning("Docker availability check failed with panic, falling back to emulation mode");
false false
} }
}; };
@@ -104,12 +106,12 @@ impl App {
"Docker is not available or unresponsive. Using emulation mode instead." "Docker is not available or unresponsive. Using emulation mode instead."
.to_string(), .to_string(),
); );
logging::warning( wrkflw_logging::warning(
"Docker is not available or unresponsive. Using emulation mode instead.", "Docker is not available or unresponsive. Using emulation mode instead.",
); );
RuntimeType::Emulation RuntimeType::Emulation
} else { } else {
logging::info("Docker is available, using Docker runtime"); wrkflw_logging::info("Docker is available, using Docker runtime");
RuntimeType::Docker RuntimeType::Docker
} }
} }
@@ -119,8 +121,10 @@ impl App {
// Use a very short timeout to prevent blocking the UI // Use a very short timeout to prevent blocking the UI
let result = std::thread::scope(|s| { let result = std::thread::scope(|s| {
let handle = s.spawn(|| { let handle = s.spawn(|| {
utils::fd::with_stderr_to_null(executor::podman::is_available) wrkflw_utils::fd::with_stderr_to_null(
.unwrap_or(false) wrkflw_executor::podman::is_available,
)
.unwrap_or(false)
}); });
// Set a short timeout for the thread // Set a short timeout for the thread
@@ -135,7 +139,7 @@ impl App {
} }
// If we reach here, the check took too long // If we reach here, the check took too long
logging::warning( wrkflw_logging::warning(
"Podman availability check timed out, falling back to emulation mode", "Podman availability check timed out, falling back to emulation mode",
); );
false false
@@ -144,7 +148,7 @@ impl App {
}) { }) {
Ok(result) => result, Ok(result) => result,
Err(_) => { Err(_) => {
logging::warning("Podman availability check failed with panic, falling back to emulation mode"); wrkflw_logging::warning("Podman availability check failed with panic, falling back to emulation mode");
false false
} }
}; };
@@ -154,12 +158,12 @@ impl App {
"Podman is not available or unresponsive. Using emulation mode instead." "Podman is not available or unresponsive. Using emulation mode instead."
.to_string(), .to_string(),
); );
logging::warning( wrkflw_logging::warning(
"Podman is not available or unresponsive. Using emulation mode instead.", "Podman is not available or unresponsive. Using emulation mode instead.",
); );
RuntimeType::Emulation RuntimeType::Emulation
} else { } else {
logging::info("Podman is available, using Podman runtime"); wrkflw_logging::info("Podman is available, using Podman runtime");
RuntimeType::Podman RuntimeType::Podman
} }
} }
@@ -227,7 +231,7 @@ impl App {
let timestamp = Local::now().format("%H:%M:%S").to_string(); let timestamp = Local::now().format("%H:%M:%S").to_string();
self.logs self.logs
.push(format!("[{}] Switched to {} mode", timestamp, mode)); .push(format!("[{}] Switched to {} mode", timestamp, mode));
logging::info(&format!("Switched to {} mode", mode)); wrkflw_logging::info(&format!("Switched to {} mode", mode));
} }
pub fn runtime_type_name(&self) -> &str { pub fn runtime_type_name(&self) -> &str {
@@ -445,7 +449,7 @@ impl App {
let timestamp = Local::now().format("%H:%M:%S").to_string(); let timestamp = Local::now().format("%H:%M:%S").to_string();
self.logs self.logs
.push(format!("[{}] Starting workflow execution...", timestamp)); .push(format!("[{}] Starting workflow execution...", timestamp));
logging::info("Starting workflow execution..."); wrkflw_logging::info("Starting workflow execution...");
} }
} }
@@ -453,7 +457,7 @@ impl App {
pub fn process_execution_result( pub fn process_execution_result(
&mut self, &mut self,
workflow_idx: usize, workflow_idx: usize,
result: Result<(Vec<executor::JobResult>, ()), String>, result: Result<(Vec<wrkflw_executor::JobResult>, ()), String>,
) { ) {
if workflow_idx >= self.workflows.len() { if workflow_idx >= self.workflows.len() {
let timestamp = Local::now().format("%H:%M:%S").to_string(); let timestamp = Local::now().format("%H:%M:%S").to_string();
@@ -461,7 +465,7 @@ impl App {
"[{}] Error: Invalid workflow index received", "[{}] Error: Invalid workflow index received",
timestamp timestamp
)); ));
logging::error("Invalid workflow index received in process_execution_result"); wrkflw_logging::error("Invalid workflow index received in process_execution_result");
return; return;
} }
@@ -490,15 +494,15 @@ impl App {
.push(format!("[{}] Operation completed successfully.", timestamp)); .push(format!("[{}] Operation completed successfully.", timestamp));
execution_details.progress = 1.0; execution_details.progress = 1.0;
// Convert executor::JobResult to our JobExecution struct // Convert wrkflw_executor::JobResult to our JobExecution struct
execution_details.jobs = jobs execution_details.jobs = jobs
.iter() .iter()
.map(|job_result| JobExecution { .map(|job_result| JobExecution {
name: job_result.name.clone(), name: job_result.name.clone(),
status: match job_result.status { status: match job_result.status {
executor::JobStatus::Success => JobStatus::Success, wrkflw_executor::JobStatus::Success => JobStatus::Success,
executor::JobStatus::Failure => JobStatus::Failure, wrkflw_executor::JobStatus::Failure => JobStatus::Failure,
executor::JobStatus::Skipped => JobStatus::Skipped, wrkflw_executor::JobStatus::Skipped => JobStatus::Skipped,
}, },
steps: job_result steps: job_result
.steps .steps
@@ -506,9 +510,9 @@ impl App {
.map(|step_result| StepExecution { .map(|step_result| StepExecution {
name: step_result.name.clone(), name: step_result.name.clone(),
status: match step_result.status { status: match step_result.status {
executor::StepStatus::Success => StepStatus::Success, wrkflw_executor::StepStatus::Success => StepStatus::Success,
executor::StepStatus::Failure => StepStatus::Failure, wrkflw_executor::StepStatus::Failure => StepStatus::Failure,
executor::StepStatus::Skipped => StepStatus::Skipped, wrkflw_executor::StepStatus::Skipped => StepStatus::Skipped,
}, },
output: step_result.output.clone(), output: step_result.output.clone(),
}) })
@@ -547,7 +551,7 @@ impl App {
"[{}] Workflow '{}' completed successfully!", "[{}] Workflow '{}' completed successfully!",
timestamp, workflow.name timestamp, workflow.name
)); ));
logging::info(&format!( wrkflw_logging::info(&format!(
"[{}] Workflow '{}' completed successfully!", "[{}] Workflow '{}' completed successfully!",
timestamp, workflow.name timestamp, workflow.name
)); ));
@@ -559,7 +563,7 @@ impl App {
"[{}] Workflow '{}' failed: {}", "[{}] Workflow '{}' failed: {}",
timestamp, workflow.name, e timestamp, workflow.name, e
)); ));
logging::error(&format!( wrkflw_logging::error(&format!(
"[{}] Workflow '{}' failed: {}", "[{}] Workflow '{}' failed: {}",
timestamp, workflow.name, e timestamp, workflow.name, e
)); ));
@@ -585,7 +589,7 @@ impl App {
self.current_execution = Some(next); self.current_execution = Some(next);
self.logs self.logs
.push(format!("Executing workflow: {}", self.workflows[next].name)); .push(format!("Executing workflow: {}", self.workflows[next].name));
logging::info(&format!( wrkflw_logging::info(&format!(
"Executing workflow: {}", "Executing workflow: {}",
self.workflows[next].name self.workflows[next].name
)); ));
@@ -688,7 +692,7 @@ impl App {
for log in &self.logs { for log in &self.logs {
all_logs.push(log.clone()); all_logs.push(log.clone());
} }
for log in logging::get_logs() { for log in wrkflw_logging::get_logs() {
all_logs.push(log.clone()); all_logs.push(log.clone());
} }
@@ -780,7 +784,7 @@ impl App {
// Scroll logs down // Scroll logs down
pub fn scroll_logs_down(&mut self) { pub fn scroll_logs_down(&mut self) {
// Get total log count including system logs // Get total log count including system logs
let total_logs = self.logs.len() + logging::get_logs().len(); let total_logs = self.logs.len() + wrkflw_logging::get_logs().len();
if total_logs > 0 { if total_logs > 0 {
self.log_scroll = (self.log_scroll + 1).min(total_logs - 1); self.log_scroll = (self.log_scroll + 1).min(total_logs - 1);
} }
@@ -834,7 +838,9 @@ impl App {
let timestamp = Local::now().format("%H:%M:%S").to_string(); let timestamp = Local::now().format("%H:%M:%S").to_string();
self.logs self.logs
.push(format!("[{}] Error: Invalid workflow selection", timestamp)); .push(format!("[{}] Error: Invalid workflow selection", timestamp));
logging::error("Invalid workflow selection in trigger_selected_workflow"); wrkflw_logging::error(
"Invalid workflow selection in trigger_selected_workflow",
);
return; return;
} }
@@ -844,7 +850,7 @@ impl App {
"[{}] Triggering workflow: {}", "[{}] Triggering workflow: {}",
timestamp, workflow.name timestamp, workflow.name
)); ));
logging::info(&format!("Triggering workflow: {}", workflow.name)); wrkflw_logging::info(&format!("Triggering workflow: {}", workflow.name));
// Clone necessary values for the async task // Clone necessary values for the async task
let workflow_name = workflow.name.clone(); let workflow_name = workflow.name.clone();
@@ -877,19 +883,19 @@ impl App {
// Send the result back to the main thread // Send the result back to the main thread
if let Err(e) = tx_clone.send((selected_idx, result)) { if let Err(e) = tx_clone.send((selected_idx, result)) {
logging::error(&format!("Error sending trigger result: {}", e)); wrkflw_logging::error(&format!("Error sending trigger result: {}", e));
} }
}); });
} else { } else {
let timestamp = Local::now().format("%H:%M:%S").to_string(); let timestamp = Local::now().format("%H:%M:%S").to_string();
self.logs self.logs
.push(format!("[{}] No workflow selected to trigger", timestamp)); .push(format!("[{}] No workflow selected to trigger", timestamp));
logging::warning("No workflow selected to trigger"); wrkflw_logging::warning("No workflow selected to trigger");
} }
} else { } else {
self.logs self.logs
.push("No workflow selected to trigger".to_string()); .push("No workflow selected to trigger".to_string());
logging::warning("No workflow selected to trigger"); wrkflw_logging::warning("No workflow selected to trigger");
} }
} }
@@ -902,7 +908,7 @@ impl App {
"[{}] Debug: No workflow selected for reset", "[{}] Debug: No workflow selected for reset",
timestamp timestamp
)); ));
logging::warning("No workflow selected for reset"); wrkflw_logging::warning("No workflow selected for reset");
return; return;
} }
@@ -939,7 +945,7 @@ impl App {
"[{}] Reset workflow '{}' from {} state to NotStarted - status is now {:?}", "[{}] Reset workflow '{}' from {} state to NotStarted - status is now {:?}",
timestamp, workflow.name, old_status, workflow.status timestamp, workflow.name, old_status, workflow.status
)); ));
logging::info(&format!( wrkflw_logging::info(&format!(
"Reset workflow '{}' from {} state to NotStarted - status is now {:?}", "Reset workflow '{}' from {} state to NotStarted - status is now {:?}",
workflow.name, old_status, workflow.status workflow.name, old_status, workflow.status
)); ));

View File

@@ -2,12 +2,12 @@
use crate::app::App; use crate::app::App;
use crate::models::{ExecutionResultMsg, WorkflowExecution, WorkflowStatus}; use crate::models::{ExecutionResultMsg, WorkflowExecution, WorkflowStatus};
use chrono::Local; use chrono::Local;
use evaluator::evaluate_workflow_file;
use executor::{self, JobStatus, RuntimeType, StepStatus};
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::mpsc; use std::sync::mpsc;
use std::thread; use std::thread;
use wrkflw_evaluator::evaluate_workflow_file;
use wrkflw_executor::{self, JobStatus, RuntimeType, StepStatus};
// Validate a workflow or directory containing workflows // Validate a workflow or directory containing workflows
pub fn validate_workflow(path: &Path, verbose: bool) -> io::Result<()> { pub fn validate_workflow(path: &Path, verbose: bool) -> io::Result<()> {
@@ -20,7 +20,7 @@ pub fn validate_workflow(path: &Path, verbose: bool) -> io::Result<()> {
let entry = entry?; let entry = entry?;
let entry_path = entry.path(); let entry_path = entry.path();
if entry_path.is_file() && utils::is_workflow_file(&entry_path) { if entry_path.is_file() && wrkflw_utils::is_workflow_file(&entry_path) {
workflows.push(entry_path); workflows.push(entry_path);
} }
} }
@@ -105,18 +105,18 @@ pub async fn execute_workflow_cli(
// Check container runtime availability if container runtime is selected // Check container runtime availability if container runtime is selected
let runtime_type = match runtime_type { let runtime_type = match runtime_type {
RuntimeType::Docker => { RuntimeType::Docker => {
if !executor::docker::is_available() { if !wrkflw_executor::docker::is_available() {
println!("⚠️ Docker is not available. Using emulation mode instead."); println!("⚠️ Docker is not available. Using emulation mode instead.");
logging::warning("Docker is not available. Using emulation mode instead."); wrkflw_logging::warning("Docker is not available. Using emulation mode instead.");
RuntimeType::Emulation RuntimeType::Emulation
} else { } else {
RuntimeType::Docker RuntimeType::Docker
} }
} }
RuntimeType::Podman => { RuntimeType::Podman => {
if !executor::podman::is_available() { if !wrkflw_executor::podman::is_available() {
println!("⚠️ Podman is not available. Using emulation mode instead."); println!("⚠️ Podman is not available. Using emulation mode instead.");
logging::warning("Podman is not available. Using emulation mode instead."); wrkflw_logging::warning("Podman is not available. Using emulation mode instead.");
RuntimeType::Emulation RuntimeType::Emulation
} else { } else {
RuntimeType::Podman RuntimeType::Podman
@@ -129,20 +129,20 @@ pub async fn execute_workflow_cli(
println!("Runtime mode: {:?}", runtime_type); println!("Runtime mode: {:?}", runtime_type);
// Log the start of the execution in debug mode with more details // Log the start of the execution in debug mode with more details
logging::debug(&format!( wrkflw_logging::debug(&format!(
"Starting workflow execution: path={}, runtime={:?}, verbose={}", "Starting workflow execution: path={}, runtime={:?}, verbose={}",
path.display(), path.display(),
runtime_type, runtime_type,
verbose verbose
)); ));
let config = executor::ExecutionConfig { let config = wrkflw_executor::ExecutionConfig {
runtime_type, runtime_type,
verbose, verbose,
preserve_containers_on_failure: false, // Default for this path preserve_containers_on_failure: false, // Default for this path
}; };
match executor::execute_workflow(path, config).await { match wrkflw_executor::execute_workflow(path, config).await {
Ok(result) => { Ok(result) => {
println!("\nWorkflow execution results:"); println!("\nWorkflow execution results:");
@@ -166,7 +166,7 @@ pub async fn execute_workflow_cli(
println!("-------------------------"); println!("-------------------------");
// Log the job details for debug purposes // Log the job details for debug purposes
logging::debug(&format!("Job: {}, Status: {:?}", job.name, job.status)); wrkflw_logging::debug(&format!("Job: {}, Status: {:?}", job.name, job.status));
for step in job.steps.iter() { for step in job.steps.iter() {
match step.status { match step.status {
@@ -202,7 +202,7 @@ pub async fn execute_workflow_cli(
} }
// Show command/run details in debug mode // Show command/run details in debug mode
if logging::get_log_level() <= logging::LogLevel::Debug { if wrkflw_logging::get_log_level() <= wrkflw_logging::LogLevel::Debug {
if let Some(cmd_output) = step if let Some(cmd_output) = step
.output .output
.lines() .lines()
@@ -242,7 +242,7 @@ pub async fn execute_workflow_cli(
} }
// Always log the step details for debug purposes // Always log the step details for debug purposes
logging::debug(&format!( wrkflw_logging::debug(&format!(
"Step: {}, Status: {:?}, Output length: {} lines", "Step: {}, Status: {:?}, Output length: {} lines",
step.name, step.name,
step.status, step.status,
@@ -250,10 +250,10 @@ pub async fn execute_workflow_cli(
)); ));
// In debug mode, log all step output // In debug mode, log all step output
if logging::get_log_level() == logging::LogLevel::Debug if wrkflw_logging::get_log_level() == wrkflw_logging::LogLevel::Debug
&& !step.output.trim().is_empty() && !step.output.trim().is_empty()
{ {
logging::debug(&format!( wrkflw_logging::debug(&format!(
"Step output for '{}': \n{}", "Step output for '{}': \n{}",
step.name, step.output step.name, step.output
)); ));
@@ -265,7 +265,7 @@ pub async fn execute_workflow_cli(
println!("\n❌ Workflow completed with failures"); println!("\n❌ Workflow completed with failures");
// In the case of failure, we'll also inform the user about the debug option // In the case of failure, we'll also inform the user about the debug option
// if they're not already using it // if they're not already using it
if logging::get_log_level() > logging::LogLevel::Debug { if wrkflw_logging::get_log_level() > wrkflw_logging::LogLevel::Debug {
println!(" Run with --debug for more detailed output"); println!(" Run with --debug for more detailed output");
} }
} else { } else {
@@ -276,7 +276,7 @@ pub async fn execute_workflow_cli(
} }
Err(e) => { Err(e) => {
println!("❌ Failed to execute workflow: {}", e); println!("❌ Failed to execute workflow: {}", e);
logging::error(&format!("Failed to execute workflow: {}", e)); wrkflw_logging::error(&format!("Failed to execute workflow: {}", e));
Err(io::Error::other(e)) Err(io::Error::other(e))
} }
} }
@@ -286,7 +286,7 @@ pub async fn execute_workflow_cli(
pub async fn execute_curl_trigger( pub async fn execute_curl_trigger(
workflow_name: &str, workflow_name: &str,
branch: Option<&str>, branch: Option<&str>,
) -> Result<(Vec<executor::JobResult>, ()), String> { ) -> Result<(Vec<wrkflw_executor::JobResult>, ()), String> {
// Get GitHub token // Get GitHub token
let token = std::env::var("GITHUB_TOKEN").map_err(|_| { let token = std::env::var("GITHUB_TOKEN").map_err(|_| {
"GitHub token not found. Please set GITHUB_TOKEN environment variable".to_string() "GitHub token not found. Please set GITHUB_TOKEN environment variable".to_string()
@@ -294,13 +294,13 @@ pub async fn execute_curl_trigger(
// Debug log to check if GITHUB_TOKEN is set // Debug log to check if GITHUB_TOKEN is set
match std::env::var("GITHUB_TOKEN") { match std::env::var("GITHUB_TOKEN") {
Ok(token) => logging::info(&format!("GITHUB_TOKEN is set: {}", &token[..5])), // Log first 5 characters for security Ok(token) => wrkflw_logging::info(&format!("GITHUB_TOKEN is set: {}", &token[..5])), // Log first 5 characters for security
Err(_) => logging::error("GITHUB_TOKEN is not set"), Err(_) => wrkflw_logging::error("GITHUB_TOKEN is not set"),
} }
// Get repository information // Get repository information
let repo_info = let repo_info = wrkflw_github::get_repo_info()
github::get_repo_info().map_err(|e| format!("Failed to get repository info: {}", e))?; .map_err(|e| format!("Failed to get repository info: {}", e))?;
// Determine branch to use // Determine branch to use
let branch_ref = branch.unwrap_or(&repo_info.default_branch); let branch_ref = branch.unwrap_or(&repo_info.default_branch);
@@ -315,7 +315,7 @@ pub async fn execute_curl_trigger(
workflow_name workflow_name
}; };
logging::info(&format!("Using workflow name: {}", workflow_name)); wrkflw_logging::info(&format!("Using workflow name: {}", workflow_name));
// Construct JSON payload // Construct JSON payload
let payload = serde_json::json!({ let payload = serde_json::json!({
@@ -328,7 +328,7 @@ pub async fn execute_curl_trigger(
repo_info.owner, repo_info.repo, workflow_name repo_info.owner, repo_info.repo, workflow_name
); );
logging::info(&format!("Triggering workflow at URL: {}", url)); wrkflw_logging::info(&format!("Triggering workflow at URL: {}", url));
// Create a reqwest client // Create a reqwest client
let client = reqwest::Client::new(); let client = reqwest::Client::new();
@@ -362,12 +362,12 @@ pub async fn execute_curl_trigger(
); );
// Create a job result structure // Create a job result structure
let job_result = executor::JobResult { let job_result = wrkflw_executor::JobResult {
name: "GitHub Trigger".to_string(), name: "GitHub Trigger".to_string(),
status: executor::JobStatus::Success, status: wrkflw_executor::JobStatus::Success,
steps: vec![executor::StepResult { steps: vec![wrkflw_executor::StepResult {
name: "Remote Trigger".to_string(), name: "Remote Trigger".to_string(),
status: executor::StepStatus::Success, status: wrkflw_executor::StepStatus::Success,
output: success_msg, output: success_msg,
}], }],
logs: "Workflow triggered remotely on GitHub".to_string(), logs: "Workflow triggered remotely on GitHub".to_string(),
@@ -391,13 +391,13 @@ pub fn start_next_workflow_execution(
if verbose { if verbose {
app.logs app.logs
.push("Verbose mode: Step outputs will be displayed in full".to_string()); .push("Verbose mode: Step outputs will be displayed in full".to_string());
logging::info("Verbose mode: Step outputs will be displayed in full"); wrkflw_logging::info("Verbose mode: Step outputs will be displayed in full");
} else { } else {
app.logs.push( app.logs.push(
"Standard mode: Only step status will be shown (use --verbose for full output)" "Standard mode: Only step status will be shown (use --verbose for full output)"
.to_string(), .to_string(),
); );
logging::info( wrkflw_logging::info(
"Standard mode: Only step status will be shown (use --verbose for full output)", "Standard mode: Only step status will be shown (use --verbose for full output)",
); );
} }
@@ -406,21 +406,24 @@ pub fn start_next_workflow_execution(
let runtime_type = match app.runtime_type { let runtime_type = match app.runtime_type {
RuntimeType::Docker => { RuntimeType::Docker => {
// Use safe FD redirection to check Docker availability // Use safe FD redirection to check Docker availability
let is_docker_available = let is_docker_available = match wrkflw_utils::fd::with_stderr_to_null(
match utils::fd::with_stderr_to_null(executor::docker::is_available) { wrkflw_executor::docker::is_available,
Ok(result) => result, ) {
Err(_) => { Ok(result) => result,
logging::debug( Err(_) => {
"Failed to redirect stderr when checking Docker availability.", wrkflw_logging::debug(
); "Failed to redirect stderr when checking Docker availability.",
false );
} false
}; }
};
if !is_docker_available { if !is_docker_available {
app.logs app.logs
.push("Docker is not available. Using emulation mode instead.".to_string()); .push("Docker is not available. Using emulation mode instead.".to_string());
logging::warning("Docker is not available. Using emulation mode instead."); wrkflw_logging::warning(
"Docker is not available. Using emulation mode instead.",
);
RuntimeType::Emulation RuntimeType::Emulation
} else { } else {
RuntimeType::Docker RuntimeType::Docker
@@ -428,21 +431,24 @@ pub fn start_next_workflow_execution(
} }
RuntimeType::Podman => { RuntimeType::Podman => {
// Use safe FD redirection to check Podman availability // Use safe FD redirection to check Podman availability
let is_podman_available = let is_podman_available = match wrkflw_utils::fd::with_stderr_to_null(
match utils::fd::with_stderr_to_null(executor::podman::is_available) { wrkflw_executor::podman::is_available,
Ok(result) => result, ) {
Err(_) => { Ok(result) => result,
logging::debug( Err(_) => {
"Failed to redirect stderr when checking Podman availability.", wrkflw_logging::debug(
); "Failed to redirect stderr when checking Podman availability.",
false );
} false
}; }
};
if !is_podman_available { if !is_podman_available {
app.logs app.logs
.push("Podman is not available. Using emulation mode instead.".to_string()); .push("Podman is not available. Using emulation mode instead.".to_string());
logging::warning("Podman is not available. Using emulation mode instead."); wrkflw_logging::warning(
"Podman is not available. Using emulation mode instead.",
);
RuntimeType::Emulation RuntimeType::Emulation
} else { } else {
RuntimeType::Podman RuntimeType::Podman
@@ -487,21 +493,21 @@ pub fn start_next_workflow_execution(
Ok(validation_result) => { Ok(validation_result) => {
// Create execution result based on validation // Create execution result based on validation
let status = if validation_result.is_valid { let status = if validation_result.is_valid {
executor::JobStatus::Success wrkflw_executor::JobStatus::Success
} else { } else {
executor::JobStatus::Failure wrkflw_executor::JobStatus::Failure
}; };
// Create a synthetic job result for validation // Create a synthetic job result for validation
let jobs = vec![executor::JobResult { let jobs = vec![wrkflw_executor::JobResult {
name: "Validation".to_string(), name: "Validation".to_string(),
status, status,
steps: vec![executor::StepResult { steps: vec![wrkflw_executor::StepResult {
name: "Validator".to_string(), name: "Validator".to_string(),
status: if validation_result.is_valid { status: if validation_result.is_valid {
executor::StepStatus::Success wrkflw_executor::StepStatus::Success
} else { } else {
executor::StepStatus::Failure wrkflw_executor::StepStatus::Failure
}, },
output: validation_result.issues.join("\n"), output: validation_result.issues.join("\n"),
}], }],
@@ -521,15 +527,15 @@ pub fn start_next_workflow_execution(
} }
} else { } else {
// Use safe FD redirection for execution // Use safe FD redirection for execution
let config = executor::ExecutionConfig { let config = wrkflw_executor::ExecutionConfig {
runtime_type, runtime_type,
verbose, verbose,
preserve_containers_on_failure, preserve_containers_on_failure,
}; };
let execution_result = utils::fd::with_stderr_to_null(|| { let execution_result = wrkflw_utils::fd::with_stderr_to_null(|| {
futures::executor::block_on(async { futures::executor::block_on(async {
executor::execute_workflow(&workflow_path, config).await wrkflw_executor::execute_workflow(&workflow_path, config).await
}) })
}) })
.map_err(|e| format!("Failed to redirect stderr during execution: {}", e))?; .map_err(|e| format!("Failed to redirect stderr during execution: {}", e))?;
@@ -546,7 +552,7 @@ pub fn start_next_workflow_execution(
// Only send if we get a valid result // Only send if we get a valid result
if let Err(e) = tx_clone_inner.send((next_idx, result)) { if let Err(e) = tx_clone_inner.send((next_idx, result)) {
logging::error(&format!("Error sending execution result: {}", e)); wrkflw_logging::error(&format!("Error sending execution result: {}", e));
} }
}); });
} else { } else {
@@ -554,6 +560,6 @@ pub fn start_next_workflow_execution(
let timestamp = Local::now().format("%H:%M:%S").to_string(); let timestamp = Local::now().format("%H:%M:%S").to_string();
app.logs app.logs
.push(format!("[{}] All workflows completed execution", timestamp)); .push(format!("[{}] All workflows completed execution", timestamp));
logging::info("All workflows completed execution"); wrkflw_logging::info("All workflows completed execution");
} }
} }

View File

@@ -1,10 +1,10 @@
// UI Models for wrkflw // UI Models for wrkflw
use chrono::Local; use chrono::Local;
use executor::{JobStatus, StepStatus};
use std::path::PathBuf; use std::path::PathBuf;
use wrkflw_executor::{JobStatus, StepStatus};
/// Type alias for the complex execution result type /// Type alias for the complex execution result type
pub type ExecutionResultMsg = (usize, Result<(Vec<executor::JobResult>, ()), String>); pub type ExecutionResultMsg = (usize, Result<(Vec<wrkflw_executor::JobResult>, ()), String>);
/// Represents an individual workflow file /// Represents an individual workflow file
pub struct Workflow { pub struct Workflow {

View File

@@ -1,7 +1,7 @@
// UI utilities // UI utilities
use crate::models::{Workflow, WorkflowStatus}; use crate::models::{Workflow, WorkflowStatus};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use utils::is_workflow_file; use wrkflw_utils::is_workflow_file;
/// Find and load all workflow files in a directory /// Find and load all workflow files in a directory
pub fn load_workflows(dir_path: &Path) -> Vec<Workflow> { pub fn load_workflows(dir_path: &Path) -> Vec<Workflow> {

View File

@@ -145,15 +145,17 @@ pub fn render_execution_tab(
.iter() .iter()
.map(|job| { .map(|job| {
let status_symbol = match job.status { let status_symbol = match job.status {
executor::JobStatus::Success => "", wrkflw_executor::JobStatus::Success => "",
executor::JobStatus::Failure => "", wrkflw_executor::JobStatus::Failure => "",
executor::JobStatus::Skipped => "", wrkflw_executor::JobStatus::Skipped => "",
}; };
let status_style = match job.status { let status_style = match job.status {
executor::JobStatus::Success => Style::default().fg(Color::Green), wrkflw_executor::JobStatus::Success => {
executor::JobStatus::Failure => Style::default().fg(Color::Red), Style::default().fg(Color::Green)
executor::JobStatus::Skipped => Style::default().fg(Color::Gray), }
wrkflw_executor::JobStatus::Failure => Style::default().fg(Color::Red),
wrkflw_executor::JobStatus::Skipped => Style::default().fg(Color::Gray),
}; };
// Count completed and total steps // Count completed and total steps
@@ -162,8 +164,8 @@ pub fn render_execution_tab(
.steps .steps
.iter() .iter()
.filter(|s| { .filter(|s| {
s.status == executor::StepStatus::Success s.status == wrkflw_executor::StepStatus::Success
|| s.status == executor::StepStatus::Failure || s.status == wrkflw_executor::StepStatus::Failure
}) })
.count(); .count();

View File

@@ -46,15 +46,15 @@ pub fn render_job_detail_view(
// Job title section // Job title section
let status_text = match job.status { let status_text = match job.status {
executor::JobStatus::Success => "Success", wrkflw_executor::JobStatus::Success => "Success",
executor::JobStatus::Failure => "Failed", wrkflw_executor::JobStatus::Failure => "Failed",
executor::JobStatus::Skipped => "Skipped", wrkflw_executor::JobStatus::Skipped => "Skipped",
}; };
let status_style = match job.status { let status_style = match job.status {
executor::JobStatus::Success => Style::default().fg(Color::Green), wrkflw_executor::JobStatus::Success => Style::default().fg(Color::Green),
executor::JobStatus::Failure => Style::default().fg(Color::Red), wrkflw_executor::JobStatus::Failure => Style::default().fg(Color::Red),
executor::JobStatus::Skipped => Style::default().fg(Color::Yellow), wrkflw_executor::JobStatus::Skipped => Style::default().fg(Color::Yellow),
}; };
let job_title = Paragraph::new(vec![ let job_title = Paragraph::new(vec![
@@ -101,15 +101,19 @@ pub fn render_job_detail_view(
let rows = job.steps.iter().map(|step| { let rows = job.steps.iter().map(|step| {
let status_symbol = match step.status { let status_symbol = match step.status {
executor::StepStatus::Success => "", wrkflw_executor::StepStatus::Success => "",
executor::StepStatus::Failure => "", wrkflw_executor::StepStatus::Failure => "",
executor::StepStatus::Skipped => "", wrkflw_executor::StepStatus::Skipped => "",
}; };
let status_style = match step.status { let status_style = match step.status {
executor::StepStatus::Success => Style::default().fg(Color::Green), wrkflw_executor::StepStatus::Success => {
executor::StepStatus::Failure => Style::default().fg(Color::Red), Style::default().fg(Color::Green)
executor::StepStatus::Skipped => Style::default().fg(Color::Gray), }
wrkflw_executor::StepStatus::Failure => Style::default().fg(Color::Red),
wrkflw_executor::StepStatus::Skipped => {
Style::default().fg(Color::Gray)
}
}; };
Row::new(vec![ Row::new(vec![
@@ -147,15 +151,21 @@ pub fn render_job_detail_view(
// Show step output with proper styling // Show step output with proper styling
let status_text = match step.status { let status_text = match step.status {
executor::StepStatus::Success => "Success", wrkflw_executor::StepStatus::Success => "Success",
executor::StepStatus::Failure => "Failed", wrkflw_executor::StepStatus::Failure => "Failed",
executor::StepStatus::Skipped => "Skipped", wrkflw_executor::StepStatus::Skipped => "Skipped",
}; };
let status_style = match step.status { let status_style = match step.status {
executor::StepStatus::Success => Style::default().fg(Color::Green), wrkflw_executor::StepStatus::Success => {
executor::StepStatus::Failure => Style::default().fg(Color::Red), Style::default().fg(Color::Green)
executor::StepStatus::Skipped => Style::default().fg(Color::Yellow), }
wrkflw_executor::StepStatus::Failure => {
Style::default().fg(Color::Red)
}
wrkflw_executor::StepStatus::Skipped => {
Style::default().fg(Color::Yellow)
}
}; };
let mut output_text = step.output.clone(); let mut output_text = step.output.clone();

View File

@@ -151,7 +151,7 @@ pub fn render_logs_tab(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &App, a
} }
// Process system logs // Process system logs
for log in logging::get_logs() { for log in wrkflw_logging::get_logs() {
all_logs.push(log.clone()); all_logs.push(log.clone());
} }

View File

@@ -1,6 +1,5 @@
// Status bar rendering // Status bar rendering
use crate::app::App; use crate::app::App;
use executor::RuntimeType;
use ratatui::{ use ratatui::{
backend::CrosstermBackend, backend::CrosstermBackend,
layout::{Alignment, Rect}, layout::{Alignment, Rect},
@@ -10,6 +9,7 @@ use ratatui::{
Frame, Frame,
}; };
use std::io; use std::io;
use wrkflw_executor::RuntimeType;
// Render the status bar // Render the status bar
pub fn render_status_bar(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &App, area: Rect) { pub fn render_status_bar(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &App, area: Rect) {
@@ -50,16 +50,17 @@ pub fn render_status_bar(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &App,
match app.runtime_type { match app.runtime_type {
RuntimeType::Docker => { RuntimeType::Docker => {
// Check Docker silently using safe FD redirection // Check Docker silently using safe FD redirection
let is_docker_available = let is_docker_available = match wrkflw_utils::fd::with_stderr_to_null(
match utils::fd::with_stderr_to_null(executor::docker::is_available) { wrkflw_executor::docker::is_available,
Ok(result) => result, ) {
Err(_) => { Ok(result) => result,
logging::debug( Err(_) => {
"Failed to redirect stderr when checking Docker availability.", wrkflw_logging::debug(
); "Failed to redirect stderr when checking Docker availability.",
false );
} false
}; }
};
status_items.push(Span::raw(" ")); status_items.push(Span::raw(" "));
status_items.push(Span::styled( status_items.push(Span::styled(
@@ -79,16 +80,17 @@ pub fn render_status_bar(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &App,
} }
RuntimeType::Podman => { RuntimeType::Podman => {
// Check Podman silently using safe FD redirection // Check Podman silently using safe FD redirection
let is_podman_available = let is_podman_available = match wrkflw_utils::fd::with_stderr_to_null(
match utils::fd::with_stderr_to_null(executor::podman::is_available) { wrkflw_executor::podman::is_available,
Ok(result) => result, ) {
Err(_) => { Ok(result) => result,
logging::debug( Err(_) => {
"Failed to redirect stderr when checking Podman availability.", wrkflw_logging::debug(
); "Failed to redirect stderr when checking Podman availability.",
false );
} false
}; }
};
status_items.push(Span::raw(" ")); status_items.push(Span::raw(" "));
status_items.push(Span::styled( status_items.push(Span::styled(
@@ -159,7 +161,7 @@ pub fn render_status_bar(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &App,
} }
2 => { 2 => {
// For logs tab, show scrolling instructions // For logs tab, show scrolling instructions
let log_count = app.logs.len() + logging::get_logs().len(); let log_count = app.logs.len() + wrkflw_logging::get_logs().len();
if log_count > 0 { if log_count > 0 {
// Convert to a static string for consistent return type // Convert to a static string for consistent return type
let scroll_text = format!( let scroll_text = format!(

View File

@@ -1,13 +1,18 @@
[package] [package]
name = "utils" name = "wrkflw-utils"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
description = "utility functions for wrkflw" description = "Utility functions for wrkflw workflow execution engine"
license.workspace = true license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies] [dependencies]
# Internal crates # Internal crates
models = { path = "../models" } wrkflw-models = { path = "../models", version = "0.5.0" }
# External dependencies # External dependencies
serde.workspace = true serde.workspace = true

View File

@@ -1,14 +1,19 @@
[package] [package]
name = "validators" name = "wrkflw-validators"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
description = "validation functionality for wrkflw" description = "Workflow validation functionality for wrkflw execution engine"
license.workspace = true license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true
[dependencies] [dependencies]
# Internal crates # Internal crates
models = { path = "../models" } wrkflw-models = { path = "../models", version = "0.5.0" }
matrix = { path = "../matrix" } wrkflw-matrix = { path = "../matrix", version = "0.5.0" }
# External dependencies # External dependencies
serde.workspace = true serde.workspace = true

View File

@@ -1,4 +1,4 @@
use models::ValidationResult; use wrkflw_models::ValidationResult;
pub fn validate_action_reference( pub fn validate_action_reference(
action_ref: &str, action_ref: &str,

View File

@@ -1,6 +1,6 @@
use models::gitlab::{Job, Pipeline};
use models::ValidationResult;
use std::collections::HashMap; use std::collections::HashMap;
use wrkflw_models::gitlab::{Job, Pipeline};
use wrkflw_models::ValidationResult;
/// Validate a GitLab CI/CD pipeline /// Validate a GitLab CI/CD pipeline
pub fn validate_gitlab_pipeline(pipeline: &Pipeline) -> ValidationResult { pub fn validate_gitlab_pipeline(pipeline: &Pipeline) -> ValidationResult {
@@ -65,7 +65,7 @@ fn validate_jobs(jobs: &HashMap<String, Job>, result: &mut ValidationResult) {
// Check retry configuration // Check retry configuration
if let Some(retry) = &job.retry { if let Some(retry) = &job.retry {
match retry { match retry {
models::gitlab::Retry::MaxAttempts(attempts) => { wrkflw_models::gitlab::Retry::MaxAttempts(attempts) => {
if *attempts > 10 { if *attempts > 10 {
result.add_issue(format!( result.add_issue(format!(
"Job '{}' has excessive retry count: {}. Consider reducing to avoid resource waste", "Job '{}' has excessive retry count: {}. Consider reducing to avoid resource waste",
@@ -73,7 +73,7 @@ fn validate_jobs(jobs: &HashMap<String, Job>, result: &mut ValidationResult) {
)); ));
} }
} }
models::gitlab::Retry::Detailed { max, when: _ } => { wrkflw_models::gitlab::Retry::Detailed { max, when: _ } => {
if *max > 10 { if *max > 10 {
result.add_issue(format!( result.add_issue(format!(
"Job '{}' has excessive retry count: {}. Consider reducing to avoid resource waste", "Job '{}' has excessive retry count: {}. Consider reducing to avoid resource waste",

View File

@@ -1,6 +1,6 @@
use crate::{validate_matrix, validate_steps}; use crate::{validate_matrix, validate_steps};
use models::ValidationResult;
use serde_yaml::Value; use serde_yaml::Value;
use wrkflw_models::ValidationResult;
pub fn validate_jobs(jobs: &Value, result: &mut ValidationResult) { pub fn validate_jobs(jobs: &Value, result: &mut ValidationResult) {
if let Value::Mapping(jobs_map) = jobs { if let Value::Mapping(jobs_map) = jobs {

View File

@@ -1,5 +1,5 @@
use models::ValidationResult;
use serde_yaml::Value; use serde_yaml::Value;
use wrkflw_models::ValidationResult;
pub fn validate_matrix(matrix: &Value, result: &mut ValidationResult) { pub fn validate_matrix(matrix: &Value, result: &mut ValidationResult) {
// Check if matrix is a mapping // Check if matrix is a mapping

View File

@@ -1,7 +1,7 @@
use crate::validate_action_reference; use crate::validate_action_reference;
use models::ValidationResult;
use serde_yaml::Value; use serde_yaml::Value;
use std::collections::HashSet; use std::collections::HashSet;
use wrkflw_models::ValidationResult;
pub fn validate_steps(steps: &[Value], job_name: &str, result: &mut ValidationResult) { pub fn validate_steps(steps: &[Value], job_name: &str, result: &mut ValidationResult) {
let mut step_ids: HashSet<String> = HashSet::new(); let mut step_ids: HashSet<String> = HashSet::new();

View File

@@ -1,5 +1,5 @@
use models::ValidationResult;
use serde_yaml::Value; use serde_yaml::Value;
use wrkflw_models::ValidationResult;
pub fn validate_triggers(on: &Value, result: &mut ValidationResult) { pub fn validate_triggers(on: &Value, result: &mut ValidationResult) {
let valid_events = vec![ let valid_events = vec![

View File

@@ -12,18 +12,18 @@ license.workspace = true
[dependencies] [dependencies]
# Workspace crates # Workspace crates
models = { path = "../models" } wrkflw-models = { path = "../models", version = "0.5.0" }
executor = { path = "../executor" } wrkflw-executor = { path = "../executor", version = "0.5.0" }
github = { path = "../github" } wrkflw-github = { path = "../github", version = "0.5.0" }
gitlab = { path = "../gitlab" } wrkflw-gitlab = { path = "../gitlab", version = "0.5.0" }
logging = { path = "../logging" } wrkflw-logging = { path = "../logging", version = "0.5.0" }
matrix = { path = "../matrix" } wrkflw-matrix = { path = "../matrix", version = "0.5.0" }
parser = { path = "../parser" } wrkflw-parser = { path = "../parser", version = "0.5.0" }
runtime = { path = "../runtime" } wrkflw-runtime = { path = "../runtime", version = "0.5.0" }
ui = { path = "../ui" } wrkflw-ui = { path = "../ui", version = "0.5.0" }
utils = { path = "../utils" } wrkflw-utils = { path = "../utils", version = "0.5.0" }
validators = { path = "../validators" } wrkflw-validators = { path = "../validators", version = "0.5.0" }
evaluator = { path = "../evaluator" } wrkflw-evaluator = { path = "../evaluator", version = "0.5.0" }
# External dependencies # External dependencies
clap.workspace = true clap.workspace = true

View File

@@ -1,12 +1,12 @@
pub use evaluator; pub use wrkflw_evaluator as evaluator;
pub use executor; pub use wrkflw_executor as executor;
pub use github; pub use wrkflw_github as github;
pub use gitlab; pub use wrkflw_gitlab as gitlab;
pub use logging; pub use wrkflw_logging as logging;
pub use matrix; pub use wrkflw_matrix as matrix;
pub use models; pub use wrkflw_models as models;
pub use parser; pub use wrkflw_parser as parser;
pub use runtime; pub use wrkflw_runtime as runtime;
pub use ui; pub use wrkflw_ui as ui;
pub use utils; pub use wrkflw_utils as utils;
pub use validators; pub use wrkflw_validators as validators;

View File

@@ -14,12 +14,12 @@ enum RuntimeChoice {
Emulation, Emulation,
} }
impl From<RuntimeChoice> for executor::RuntimeType { impl From<RuntimeChoice> for wrkflw_executor::RuntimeType {
fn from(choice: RuntimeChoice) -> Self { fn from(choice: RuntimeChoice) -> Self {
match choice { match choice {
RuntimeChoice::Docker => executor::RuntimeType::Docker, RuntimeChoice::Docker => wrkflw_executor::RuntimeType::Docker,
RuntimeChoice::Podman => executor::RuntimeType::Podman, RuntimeChoice::Podman => wrkflw_executor::RuntimeType::Podman,
RuntimeChoice::Emulation => executor::RuntimeType::Emulation, RuntimeChoice::Emulation => wrkflw_executor::RuntimeType::Emulation,
} }
} }
} }
@@ -143,7 +143,7 @@ fn parse_key_val(s: &str) -> Result<(String, String), String> {
} }
// Make this function public for testing? Or move to a utils/cleanup mod? // Make this function public for testing? Or move to a utils/cleanup mod?
// Or call executor::cleanup and runtime::cleanup directly? // Or call wrkflw_executor::cleanup and wrkflw_runtime::cleanup directly?
// Let's try calling them directly for now. // Let's try calling them directly for now.
async fn cleanup_on_exit() { async fn cleanup_on_exit() {
// Clean up Docker resources if available, but don't let it block indefinitely // Clean up Docker resources if available, but don't let it block indefinitely
@@ -151,35 +151,35 @@ async fn cleanup_on_exit() {
match Docker::connect_with_local_defaults() { match Docker::connect_with_local_defaults() {
Ok(docker) => { Ok(docker) => {
// Assuming cleanup_resources exists in executor crate // Assuming cleanup_resources exists in executor crate
executor::cleanup_resources(&docker).await; wrkflw_executor::cleanup_resources(&docker).await;
} }
Err(_) => { Err(_) => {
// Docker not available // Docker not available
logging::info("Docker not available, skipping Docker cleanup"); wrkflw_logging::info("Docker not available, skipping Docker cleanup");
} }
} }
}) })
.await .await
{ {
Ok(_) => logging::debug("Docker cleanup completed successfully"), Ok(_) => wrkflw_logging::debug("Docker cleanup completed successfully"),
Err(_) => { Err(_) => wrkflw_logging::warning(
logging::warning("Docker cleanup timed out after 3 seconds, continuing with shutdown") "Docker cleanup timed out after 3 seconds, continuing with shutdown",
} ),
} }
// Always clean up emulation resources // Always clean up emulation resources
match tokio::time::timeout( match tokio::time::timeout(
std::time::Duration::from_secs(2), std::time::Duration::from_secs(2),
// Assuming cleanup_resources exists in runtime::emulation module // Assuming cleanup_resources exists in wrkflw_runtime::emulation module
runtime::emulation::cleanup_resources(), wrkflw_runtime::emulation::cleanup_resources(),
) )
.await .await
{ {
Ok(_) => logging::debug("Emulation cleanup completed successfully"), Ok(_) => wrkflw_logging::debug("Emulation cleanup completed successfully"),
Err(_) => logging::warning("Emulation cleanup timed out, continuing with shutdown"), Err(_) => wrkflw_logging::warning("Emulation cleanup timed out, continuing with shutdown"),
} }
logging::info("Resource cleanup completed"); wrkflw_logging::info("Resource cleanup completed");
} }
async fn handle_signals() { async fn handle_signals() {
@@ -207,7 +207,7 @@ async fn handle_signals() {
"Cleanup taking too long (over {} seconds), forcing exit...", "Cleanup taking too long (over {} seconds), forcing exit...",
hard_exit_time.as_secs() hard_exit_time.as_secs()
); );
logging::error("Forced exit due to cleanup timeout"); wrkflw_logging::error("Forced exit due to cleanup timeout");
std::process::exit(1); std::process::exit(1);
}); });
@@ -272,13 +272,13 @@ async fn main() {
// Set log level based on command line flags // Set log level based on command line flags
if debug { if debug {
logging::set_log_level(logging::LogLevel::Debug); wrkflw_logging::set_log_level(wrkflw_logging::LogLevel::Debug);
logging::debug("Debug mode enabled - showing detailed logs"); wrkflw_logging::debug("Debug mode enabled - showing detailed logs");
} else if verbose { } else if verbose {
logging::set_log_level(logging::LogLevel::Info); wrkflw_logging::set_log_level(wrkflw_logging::LogLevel::Info);
logging::info("Verbose mode enabled"); wrkflw_logging::info("Verbose mode enabled");
} else { } else {
logging::set_log_level(logging::LogLevel::Warning); wrkflw_logging::set_log_level(wrkflw_logging::LogLevel::Warning);
} }
// Setup a Ctrl+C handler that runs in the background // Setup a Ctrl+C handler that runs in the background
@@ -360,7 +360,7 @@ async fn main() {
gitlab, gitlab,
}) => { }) => {
// Create execution configuration // Create execution configuration
let config = executor::ExecutionConfig { let config = wrkflw_executor::ExecutionConfig {
runtime_type: runtime.clone().into(), runtime_type: runtime.clone().into(),
verbose, verbose,
preserve_containers_on_failure: *preserve_containers_on_failure, preserve_containers_on_failure: *preserve_containers_on_failure,
@@ -374,10 +374,10 @@ async fn main() {
"GitHub workflow" "GitHub workflow"
}; };
logging::info(&format!("Running {} at: {}", workflow_type, path.display())); wrkflw_logging::info(&format!("Running {} at: {}", workflow_type, path.display()));
// Execute the workflow // Execute the workflow
let result = executor::execute_workflow(path, config) let result = wrkflw_executor::execute_workflow(path, config)
.await .await
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
eprintln!("Error executing workflow: {}", e); eprintln!("Error executing workflow: {}", e);
@@ -419,15 +419,15 @@ async fn main() {
println!( println!(
" {} {} ({})", " {} {} ({})",
match job.status { match job.status {
executor::JobStatus::Success => "", wrkflw_executor::JobStatus::Success => "",
executor::JobStatus::Failure => "", wrkflw_executor::JobStatus::Failure => "",
executor::JobStatus::Skipped => "⏭️", wrkflw_executor::JobStatus::Skipped => "⏭️",
}, },
job.name, job.name,
match job.status { match job.status {
executor::JobStatus::Success => "success", wrkflw_executor::JobStatus::Success => "success",
executor::JobStatus::Failure => "failure", wrkflw_executor::JobStatus::Failure => "failure",
executor::JobStatus::Skipped => "skipped", wrkflw_executor::JobStatus::Skipped => "skipped",
} }
); );
@@ -435,15 +435,15 @@ async fn main() {
println!(" Steps:"); println!(" Steps:");
for step in job.steps { for step in job.steps {
let step_status = match step.status { let step_status = match step.status {
executor::StepStatus::Success => "", wrkflw_executor::StepStatus::Success => "",
executor::StepStatus::Failure => "", wrkflw_executor::StepStatus::Failure => "",
executor::StepStatus::Skipped => "⏭️", wrkflw_executor::StepStatus::Skipped => "⏭️",
}; };
println!(" {} {}", step_status, step.name); println!(" {} {}", step_status, step.name);
// If step failed and we're not in verbose mode, show condensed error info // If step failed and we're not in verbose mode, show condensed error info
if step.status == executor::StepStatus::Failure && !verbose { if step.status == wrkflw_executor::StepStatus::Failure && !verbose {
// Extract error information from step output // Extract error information from step output
let error_lines = step let error_lines = step
.output .output
@@ -482,7 +482,7 @@ async fn main() {
.map(|v| v.iter().cloned().collect::<HashMap<String, String>>()); .map(|v| v.iter().cloned().collect::<HashMap<String, String>>());
// Trigger the pipeline // Trigger the pipeline
if let Err(e) = gitlab::trigger_pipeline(branch.as_deref(), variables).await { if let Err(e) = wrkflw_gitlab::trigger_pipeline(branch.as_deref(), variables).await {
eprintln!("Error triggering GitLab pipeline: {}", e); eprintln!("Error triggering GitLab pipeline: {}", e);
std::process::exit(1); std::process::exit(1);
} }
@@ -497,7 +497,7 @@ async fn main() {
let runtime_type = runtime.clone().into(); let runtime_type = runtime.clone().into();
// Call the TUI implementation from the ui crate // Call the TUI implementation from the ui crate
if let Err(e) = ui::run_wrkflw_tui( if let Err(e) = wrkflw_ui::run_wrkflw_tui(
path.as_ref(), path.as_ref(),
runtime_type, runtime_type,
verbose, verbose,
@@ -520,7 +520,9 @@ async fn main() {
.map(|i| i.iter().cloned().collect::<HashMap<String, String>>()); .map(|i| i.iter().cloned().collect::<HashMap<String, String>>());
// Trigger the workflow // Trigger the workflow
if let Err(e) = github::trigger_workflow(workflow, branch.as_deref(), inputs).await { if let Err(e) =
wrkflw_github::trigger_workflow(workflow, branch.as_deref(), inputs).await
{
eprintln!("Error triggering GitHub workflow: {}", e); eprintln!("Error triggering GitHub workflow: {}", e);
std::process::exit(1); std::process::exit(1);
} }
@@ -530,10 +532,10 @@ async fn main() {
} }
None => { None => {
// Launch TUI by default when no command is provided // Launch TUI by default when no command is provided
let runtime_type = executor::RuntimeType::Docker; let runtime_type = wrkflw_executor::RuntimeType::Docker;
// Call the TUI implementation from the ui crate with default path // Call the TUI implementation from the ui crate with default path
if let Err(e) = ui::run_wrkflw_tui(None, runtime_type, verbose, false).await { if let Err(e) = wrkflw_ui::run_wrkflw_tui(None, runtime_type, verbose, false).await {
eprintln!("Error running TUI: {}", e); eprintln!("Error running TUI: {}", e);
std::process::exit(1); std::process::exit(1);
} }
@@ -547,13 +549,13 @@ fn validate_github_workflow(path: &Path, verbose: bool) -> bool {
print!("Validating GitHub workflow file: {}... ", path.display()); print!("Validating GitHub workflow file: {}... ", path.display());
// Use the ui crate's validate_workflow function // Use the ui crate's validate_workflow function
match ui::validate_workflow(path, verbose) { match wrkflw_ui::validate_workflow(path, verbose) {
Ok(_) => { Ok(_) => {
// The detailed validation output is already printed by the function // The detailed validation output is already printed by the function
// We need to check if there were validation issues // We need to check if there were validation issues
// Since ui::validate_workflow doesn't return the validation result directly, // Since wrkflw_ui::validate_workflow doesn't return the validation result directly,
// we need to call the evaluator directly to get the result // we need to call the evaluator directly to get the result
match evaluator::evaluate_workflow_file(path, verbose) { match wrkflw_evaluator::evaluate_workflow_file(path, verbose) {
Ok(result) => !result.is_valid, Ok(result) => !result.is_valid,
Err(_) => true, // Parse errors count as validation failure Err(_) => true, // Parse errors count as validation failure
} }
@@ -571,12 +573,12 @@ fn validate_gitlab_pipeline(path: &Path, verbose: bool) -> bool {
print!("Validating GitLab CI pipeline file: {}... ", path.display()); print!("Validating GitLab CI pipeline file: {}... ", path.display());
// Parse and validate the pipeline file // Parse and validate the pipeline file
match parser::gitlab::parse_pipeline(path) { match wrkflw_parser::gitlab::parse_pipeline(path) {
Ok(pipeline) => { Ok(pipeline) => {
println!("✅ Valid syntax"); println!("✅ Valid syntax");
// Additional structural validation // Additional structural validation
let validation_result = validators::validate_gitlab_pipeline(&pipeline); let validation_result = wrkflw_validators::validate_gitlab_pipeline(&pipeline);
if !validation_result.is_valid { if !validation_result.is_valid {
println!("⚠️ Validation issues:"); println!("⚠️ Validation issues:");

71
publish_crates.sh Executable file
View File

@@ -0,0 +1,71 @@
#!/bin/bash
# Simple script to publish all wrkflw crates to crates.io in dependency order
set -e
DRY_RUN=${1:-""}
if [[ "$DRY_RUN" == "--dry-run" ]]; then
echo "🧪 DRY RUN: Testing wrkflw crates publication"
else
echo "🚀 Publishing wrkflw crates to crates.io"
fi
# Check if we're logged in to crates.io
if [ ! -f ~/.cargo/credentials.toml ] && [ ! -f ~/.cargo/credentials ]; then
echo "❌ Not logged in to crates.io. Please run: cargo login <your-token>"
exit 1
fi
# Publication order (respecting dependencies)
CRATES=(
"models"
"logging"
"utils"
"matrix"
"validators"
"github"
"gitlab"
"parser"
"runtime"
"evaluator"
"executor"
"ui"
"wrkflw"
)
echo "📦 Publishing crates in dependency order..."
for crate in "${CRATES[@]}"; do
if [[ "$DRY_RUN" == "--dry-run" ]]; then
echo "Testing $crate..."
cd "crates/$crate"
cargo publish --dry-run --allow-dirty
echo "$crate dry-run successful"
else
echo "Publishing $crate..."
cd "crates/$crate"
cargo publish --allow-dirty
echo "✅ Published $crate"
fi
cd - > /dev/null
# Small delay to avoid rate limiting (except for the last crate and in dry-run)
if [[ "$crate" != "wrkflw" ]] && [[ "$DRY_RUN" != "--dry-run" ]]; then
echo " Waiting 10 seconds to avoid rate limits..."
sleep 10
fi
done
if [[ "$DRY_RUN" == "--dry-run" ]]; then
echo "🎉 All crates passed dry-run tests!"
echo ""
echo "To actually publish, run:"
echo " ./publish_crates.sh"
else
echo "🎉 All crates published successfully!"
echo ""
echo "Users can now install wrkflw with:"
echo " cargo install wrkflw"
fi