mirror of
https://github.com/bahdotsh/wrkflw.git
synced 2026-01-01 09:56:25 +01:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
537bf2f9d1 | ||
|
|
f0b6633cb8 | ||
|
|
181b5c5463 | ||
|
|
1cc3bf98b6 | ||
|
|
af8ac002e4 |
374
Cargo.lock
generated
374
Cargo.lock
generated
@@ -486,46 +486,6 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "evaluator"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"models",
|
||||
"serde_yaml",
|
||||
"validators",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "executor"
|
||||
version = "0.4.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]]
|
||||
name = "fancy-regex"
|
||||
version = "0.11.0"
|
||||
@@ -714,35 +674,6 @@ version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "github"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"models",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gitlab"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"models",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"thiserror",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.26"
|
||||
@@ -1215,28 +1146,6 @@ version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "logging"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"models",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matrix"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"indexmap 2.8.0",
|
||||
"models",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
@@ -1281,16 +1190,6 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "models"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.14"
|
||||
@@ -1511,20 +1410,6 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parser"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"jsonschema",
|
||||
"matrix",
|
||||
"models",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
@@ -1731,23 +1616,6 @@ dependencies = [
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "runtime"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures",
|
||||
"logging",
|
||||
"models",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"utils",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
@@ -2243,28 +2111,6 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "ui"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossterm 0.26.1",
|
||||
"evaluator",
|
||||
"executor",
|
||||
"futures",
|
||||
"github",
|
||||
"logging",
|
||||
"models",
|
||||
"ratatui",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"tokio",
|
||||
"utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
@@ -2324,16 +2170,6 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "utils"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"models",
|
||||
"nix",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.16.0"
|
||||
@@ -2343,16 +2179,6 @@ dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validators"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"matrix",
|
||||
"models",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
@@ -2719,7 +2545,7 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
|
||||
[[package]]
|
||||
name = "wrkflw"
|
||||
version = "0.4.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"bollard",
|
||||
"chrono",
|
||||
@@ -2727,41 +2553,215 @@ dependencies = [
|
||||
"colored",
|
||||
"crossterm 0.26.1",
|
||||
"dirs",
|
||||
"evaluator",
|
||||
"executor",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"github",
|
||||
"gitlab",
|
||||
"indexmap 2.8.0",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"logging",
|
||||
"matrix",
|
||||
"models",
|
||||
"nix",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"parser",
|
||||
"ratatui",
|
||||
"rayon",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"runtime",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"ui",
|
||||
"urlencoding",
|
||||
"utils",
|
||||
"uuid",
|
||||
"validators",
|
||||
"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.6.0"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"serde_yaml",
|
||||
"wrkflw-models",
|
||||
"wrkflw-validators",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wrkflw-executor"
|
||||
version = "0.6.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.6.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"thiserror",
|
||||
"wrkflw-models",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wrkflw-gitlab"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"thiserror",
|
||||
"urlencoding",
|
||||
"wrkflw-models",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wrkflw-logging"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"wrkflw-models",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wrkflw-matrix"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"indexmap 2.8.0",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"thiserror",
|
||||
"wrkflw-models",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wrkflw-models"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wrkflw-parser"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"jsonschema",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"wrkflw-matrix",
|
||||
"wrkflw-models",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wrkflw-runtime"
|
||||
version = "0.6.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.6.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.6.0"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"wrkflw-models",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wrkflw-validators"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"wrkflw-matrix",
|
||||
"wrkflw-models",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -5,7 +5,7 @@ members = [
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.4.0"
|
||||
version = "0.6.0"
|
||||
edition = "2021"
|
||||
description = "A GitHub Actions workflow validator and executor"
|
||||
documentation = "https://github.com/bahdotsh/wrkflw"
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
# Manual Testing Checklist for Podman Support
|
||||
|
||||
## Quick Manual Verification Steps
|
||||
|
||||
### ✅ **Step 1: CLI Help and Options**
|
||||
```bash
|
||||
./target/release/wrkflw run --help
|
||||
```
|
||||
**Verify:**
|
||||
- [ ] `--runtime` option is present
|
||||
- [ ] Shows `docker`, `podman`, `emulation` as possible values
|
||||
- [ ] Default is `docker`
|
||||
- [ ] Help text explains each option
|
||||
|
||||
### ✅ **Step 2: CLI Runtime Selection**
|
||||
```bash
|
||||
# Test each runtime option
|
||||
./target/release/wrkflw run --runtime docker test-workflows/example.yml --verbose
|
||||
./target/release/wrkflw run --runtime podman test-workflows/example.yml --verbose
|
||||
./target/release/wrkflw run --runtime emulation test-workflows/example.yml --verbose
|
||||
|
||||
# Test invalid runtime (should fail)
|
||||
./target/release/wrkflw run --runtime invalid test-workflows/example.yml
|
||||
```
|
||||
**Verify:**
|
||||
- [ ] All valid runtimes are accepted
|
||||
- [ ] Invalid runtime shows clear error message
|
||||
- [ ] Podman mode shows "Podman: Running container" in verbose logs
|
||||
- [ ] Emulation mode works without containers
|
||||
|
||||
### ✅ **Step 3: TUI Runtime Support**
|
||||
```bash
|
||||
./target/release/wrkflw tui test-workflows/
|
||||
```
|
||||
**Verify:**
|
||||
- [ ] TUI starts successfully
|
||||
- [ ] Status bar shows current runtime (bottom of screen)
|
||||
- [ ] Press `e` key to cycle through runtimes: Docker → Podman → Emulation → Docker
|
||||
- [ ] Runtime changes are reflected in status bar
|
||||
- [ ] Podman shows "Connected" or "Not Available" status
|
||||
|
||||
### ✅ **Step 4: TUI Runtime Parameter**
|
||||
```bash
|
||||
./target/release/wrkflw tui --runtime podman test-workflows/
|
||||
./target/release/wrkflw tui --runtime emulation test-workflows/
|
||||
```
|
||||
**Verify:**
|
||||
- [ ] TUI starts with specified runtime
|
||||
- [ ] Status bar reflects the specified runtime
|
||||
|
||||
### ✅ **Step 5: Container Execution Test**
|
||||
Create a simple test workflow:
|
||||
```yaml
|
||||
name: Runtime Test
|
||||
on: [workflow_dispatch]
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:20.04
|
||||
steps:
|
||||
- run: |
|
||||
echo "Runtime test execution"
|
||||
whoami
|
||||
pwd
|
||||
echo "Test completed"
|
||||
```
|
||||
|
||||
Test with different runtimes:
|
||||
```bash
|
||||
./target/release/wrkflw run --runtime podman test-runtime.yml --verbose
|
||||
./target/release/wrkflw run --runtime docker test-runtime.yml --verbose
|
||||
./target/release/wrkflw run --runtime emulation test-runtime.yml --verbose
|
||||
```
|
||||
**Verify:**
|
||||
- [ ] Podman mode runs in containers (shows container logs)
|
||||
- [ ] Docker mode runs in containers (shows container logs)
|
||||
- [ ] Emulation mode runs on host system
|
||||
- [ ] All modes produce similar output
|
||||
|
||||
### ✅ **Step 6: Error Handling**
|
||||
```bash
|
||||
# Test with Podman unavailable (temporarily rename podman binary)
|
||||
sudo mv /usr/local/bin/podman /usr/local/bin/podman.tmp 2>/dev/null || echo "podman not in /usr/local/bin"
|
||||
./target/release/wrkflw run --runtime podman test-runtime.yml
|
||||
sudo mv /usr/local/bin/podman.tmp /usr/local/bin/podman 2>/dev/null || echo "nothing to restore"
|
||||
```
|
||||
**Verify:**
|
||||
- [ ] Shows "Podman is not available. Using emulation mode instead."
|
||||
- [ ] Falls back to emulation gracefully
|
||||
- [ ] Workflow still executes successfully
|
||||
|
||||
### ✅ **Step 7: Container Preservation**
|
||||
```bash
|
||||
# Create a failing workflow
|
||||
echo 'name: Fail Test
|
||||
on: [workflow_dispatch]
|
||||
jobs:
|
||||
fail:
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:20.04
|
||||
steps:
|
||||
- run: exit 1' > test-fail.yml
|
||||
|
||||
# Test with preservation
|
||||
./target/release/wrkflw run --runtime podman --preserve-containers-on-failure test-fail.yml
|
||||
|
||||
# Check for preserved containers
|
||||
podman ps -a --filter "name=wrkflw-"
|
||||
```
|
||||
**Verify:**
|
||||
- [ ] Failed container is preserved when flag is used
|
||||
- [ ] Container can be inspected with `podman exec -it <container> bash`
|
||||
- [ ] Without flag, containers are cleaned up
|
||||
|
||||
### ✅ **Step 8: Documentation**
|
||||
**Verify:**
|
||||
- [ ] README.md mentions Podman support
|
||||
- [ ] Examples show `--runtime podman` usage
|
||||
- [ ] TUI keybind documentation mentions runtime cycling
|
||||
- [ ] Installation instructions for Podman are present
|
||||
|
||||
## Platform-Specific Tests
|
||||
|
||||
### **Linux:**
|
||||
- [ ] Podman works rootless
|
||||
- [ ] No sudo required for container operations
|
||||
- [ ] Network connectivity works in containers
|
||||
|
||||
### **macOS:**
|
||||
- [ ] Podman machine is initialized and running
|
||||
- [ ] Container execution works correctly
|
||||
- [ ] Volume mounting works for workspace
|
||||
|
||||
### **Windows:**
|
||||
- [ ] Podman Desktop or CLI is installed
|
||||
- [ ] Basic container operations work
|
||||
- [ ] Workspace mounting functions correctly
|
||||
|
||||
## Performance and Resource Tests
|
||||
|
||||
### **Memory Usage:**
|
||||
```bash
|
||||
# Monitor memory during execution
|
||||
./target/release/wrkflw run --runtime podman test-runtime.yml &
|
||||
PID=$!
|
||||
while kill -0 $PID 2>/dev/null; do
|
||||
ps -p $PID -o pid,ppid,pgid,sess,cmd,%mem,%cpu
|
||||
sleep 2
|
||||
done
|
||||
```
|
||||
**Verify:**
|
||||
- [ ] Memory usage is reasonable
|
||||
- [ ] No memory leaks during execution
|
||||
|
||||
### **Container Cleanup:**
|
||||
```bash
|
||||
# Run multiple workflows and check cleanup
|
||||
for i in {1..3}; do
|
||||
./target/release/wrkflw run --runtime podman test-runtime.yml
|
||||
done
|
||||
podman ps -a --filter "name=wrkflw-"
|
||||
```
|
||||
**Verify:**
|
||||
- [ ] No containers remain after execution
|
||||
- [ ] Cleanup is thorough and automatic
|
||||
|
||||
## Integration Tests
|
||||
|
||||
### **Complex Workflow:**
|
||||
Test with a workflow that has:
|
||||
- [ ] Multiple jobs
|
||||
- [ ] Environment variables
|
||||
- [ ] File operations
|
||||
- [ ] Network access
|
||||
- [ ] Package installation
|
||||
|
||||
### **Edge Cases:**
|
||||
- [ ] Very long-running containers
|
||||
- [ ] Large output logs
|
||||
- [ ] Network-intensive operations
|
||||
- [ ] File system intensive operations
|
||||
|
||||
## Final Verification
|
||||
|
||||
**Overall System Check:**
|
||||
- [ ] All runtimes work as expected
|
||||
- [ ] Error messages are clear and helpful
|
||||
- [ ] Performance is acceptable
|
||||
- [ ] User experience is smooth
|
||||
- [ ] Documentation is accurate and complete
|
||||
|
||||
**Sign-off:**
|
||||
- [ ] Basic functionality: ✅ PASS / ❌ FAIL
|
||||
- [ ] CLI integration: ✅ PASS / ❌ FAIL
|
||||
- [ ] TUI integration: ✅ PASS / ❌ FAIL
|
||||
- [ ] Error handling: ✅ PASS / ❌ FAIL
|
||||
- [ ] Documentation: ✅ PASS / ❌ FAIL
|
||||
|
||||
**Notes:**
|
||||
_Add any specific issues, observations, or platform-specific notes here._
|
||||
|
||||
---
|
||||
|
||||
**Testing completed by:** ________________
|
||||
**Date:** ________________
|
||||
**Platform:** ________________
|
||||
**Podman version:** ________________
|
||||
@@ -1,14 +1,19 @@
|
||||
[package]
|
||||
name = "evaluator"
|
||||
name = "wrkflw-evaluator"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Workflow evaluation for wrkflw"
|
||||
description = "Workflow evaluation functionality for wrkflw execution engine"
|
||||
license.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
validators = { path = "../validators" }
|
||||
wrkflw-models = { path = "../models", version = "0.6.0" }
|
||||
wrkflw-validators = { path = "../validators", version = "0.6.0" }
|
||||
|
||||
# External dependencies
|
||||
colored.workspace = true
|
||||
|
||||
@@ -3,8 +3,8 @@ use serde_yaml::{self, Value};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use models::ValidationResult;
|
||||
use validators::{validate_jobs, validate_triggers};
|
||||
use wrkflw_models::ValidationResult;
|
||||
use wrkflw_validators::{validate_jobs, validate_triggers};
|
||||
|
||||
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))?;
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
[package]
|
||||
name = "executor"
|
||||
name = "wrkflw-executor"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Workflow executor for wrkflw"
|
||||
description = "Workflow execution engine for wrkflw"
|
||||
license.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
parser = { path = "../parser" }
|
||||
runtime = { path = "../runtime" }
|
||||
logging = { path = "../logging" }
|
||||
matrix = { path = "../matrix" }
|
||||
utils = { path = "../utils" }
|
||||
wrkflw-models = { path = "../models", version = "0.6.0" }
|
||||
wrkflw-parser = { path = "../parser", version = "0.6.0" }
|
||||
wrkflw-runtime = { path = "../runtime", version = "0.6.0" }
|
||||
wrkflw-logging = { path = "../logging", version = "0.6.0" }
|
||||
wrkflw-matrix = { path = "../matrix", version = "0.6.0" }
|
||||
wrkflw-utils = { path = "../utils", version = "0.6.0" }
|
||||
|
||||
# External dependencies
|
||||
async-trait.workspace = true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use parser::workflow::WorkflowDefinition;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use wrkflw_parser::workflow::WorkflowDefinition;
|
||||
|
||||
pub fn resolve_dependencies(workflow: &WorkflowDefinition) -> Result<Vec<Vec<String>>, String> {
|
||||
let jobs = &workflow.jobs;
|
||||
|
||||
@@ -6,14 +6,14 @@ use bollard::{
|
||||
Docker,
|
||||
};
|
||||
use futures_util::StreamExt;
|
||||
use logging;
|
||||
use once_cell::sync::Lazy;
|
||||
use runtime::container::{ContainerError, ContainerOutput, ContainerRuntime};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
use utils;
|
||||
use utils::fd;
|
||||
use wrkflw_logging;
|
||||
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 CREATED_NETWORKS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new()));
|
||||
@@ -50,7 +50,7 @@ impl DockerRuntime {
|
||||
match CUSTOMIZED_IMAGES.lock() {
|
||||
Ok(images) => images.get(&key).cloned(),
|
||||
Err(e) => {
|
||||
logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ impl DockerRuntime {
|
||||
if let Err(e) = CUSTOMIZED_IMAGES.lock().map(|mut images| {
|
||||
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() {
|
||||
Ok(keys) => keys,
|
||||
Err(e) => {
|
||||
logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
return None;
|
||||
}
|
||||
};
|
||||
@@ -107,7 +107,7 @@ impl DockerRuntime {
|
||||
match CUSTOMIZED_IMAGES.lock() {
|
||||
Ok(images) => images.get(&key).cloned(),
|
||||
Err(e) => {
|
||||
logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ impl DockerRuntime {
|
||||
if let Err(e) = CUSTOMIZED_IMAGES.lock().map(|mut images| {
|
||||
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(_) => {
|
||||
logging::debug("Docker CLI is not available");
|
||||
wrkflw_logging::debug("Docker CLI is not available");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -331,7 +331,7 @@ pub fn is_available() -> bool {
|
||||
{
|
||||
Ok(rt) => rt,
|
||||
Err(e) => {
|
||||
logging::error(&format!(
|
||||
wrkflw_logging::error(&format!(
|
||||
"Failed to create runtime for Docker availability check: {}",
|
||||
e
|
||||
));
|
||||
@@ -352,17 +352,25 @@ pub fn is_available() -> bool {
|
||||
{
|
||||
Ok(Ok(_)) => true,
|
||||
Ok(Err(e)) => {
|
||||
logging::debug(&format!("Docker daemon ping failed: {}", e));
|
||||
wrkflw_logging::debug(&format!(
|
||||
"Docker daemon ping failed: {}",
|
||||
e
|
||||
));
|
||||
false
|
||||
}
|
||||
Err(_) => {
|
||||
logging::debug("Docker daemon ping timed out after 1 second");
|
||||
wrkflw_logging::debug(
|
||||
"Docker daemon ping timed out after 1 second",
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
logging::debug(&format!("Docker daemon connection failed: {}", e));
|
||||
wrkflw_logging::debug(&format!(
|
||||
"Docker daemon connection failed: {}",
|
||||
e
|
||||
));
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -371,7 +379,7 @@ pub fn is_available() -> bool {
|
||||
{
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::debug("Docker availability check timed out");
|
||||
wrkflw_logging::debug("Docker availability check timed out");
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -379,7 +387,9 @@ pub fn is_available() -> bool {
|
||||
}) {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::debug("Failed to redirect stderr when checking Docker availability");
|
||||
wrkflw_logging::debug(
|
||||
"Failed to redirect stderr when checking Docker availability",
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -393,7 +403,7 @@ pub fn is_available() -> bool {
|
||||
return match handle.join() {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::warning("Docker availability check thread panicked");
|
||||
wrkflw_logging::warning("Docker availability check thread panicked");
|
||||
false
|
||||
}
|
||||
};
|
||||
@@ -401,7 +411,9 @@ pub fn is_available() -> bool {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -444,19 +456,19 @@ pub async fn cleanup_resources(docker: &Docker) {
|
||||
tokio::join!(cleanup_containers(docker), cleanup_networks(docker));
|
||||
|
||||
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 {
|
||||
logging::error(&format!("Error during network cleanup: {}", e));
|
||||
wrkflw_logging::error(&format!("Error during network cleanup: {}", e));
|
||||
}
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => logging::debug("Docker cleanup completed within timeout"),
|
||||
Err(_) => {
|
||||
logging::warning("Docker cleanup timed out, some resources may not have been removed")
|
||||
}
|
||||
Ok(_) => wrkflw_logging::debug("Docker cleanup completed within timeout"),
|
||||
Err(_) => wrkflw_logging::warning(
|
||||
"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() {
|
||||
Ok(containers) => containers.clone(),
|
||||
Err(_) => {
|
||||
logging::error("Could not acquire container lock for cleanup");
|
||||
wrkflw_logging::error("Could not acquire container lock for cleanup");
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
@@ -477,7 +489,7 @@ pub async fn cleanup_containers(docker: &Docker) -> Result<(), String> {
|
||||
{
|
||||
Ok(containers) => containers,
|
||||
Err(_) => {
|
||||
logging::error("Timeout while trying to get containers for cleanup");
|
||||
wrkflw_logging::error("Timeout while trying to get containers for cleanup");
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
@@ -486,7 +498,7 @@ pub async fn cleanup_containers(docker: &Docker) -> Result<(), String> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Cleaning up {} containers",
|
||||
containers_to_cleanup.len()
|
||||
));
|
||||
@@ -500,11 +512,14 @@ pub async fn cleanup_containers(docker: &Docker) -> Result<(), String> {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(_)) => logging::debug(&format!("Stopped container: {}", container_id)),
|
||||
Ok(Err(e)) => {
|
||||
logging::warning(&format!("Error stopping container {}: {}", container_id, e))
|
||||
Ok(Ok(_)) => wrkflw_logging::debug(&format!("Stopped container: {}", container_id)),
|
||||
Ok(Err(e)) => wrkflw_logging::warning(&format!(
|
||||
"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
|
||||
@@ -514,11 +529,14 @@ pub async fn cleanup_containers(docker: &Docker) -> Result<(), String> {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(_)) => logging::debug(&format!("Removed container: {}", container_id)),
|
||||
Ok(Err(e)) => {
|
||||
logging::warning(&format!("Error removing container {}: {}", container_id, e))
|
||||
Ok(Ok(_)) => wrkflw_logging::debug(&format!("Removed container: {}", container_id)),
|
||||
Ok(Err(e)) => wrkflw_logging::warning(&format!(
|
||||
"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
|
||||
@@ -536,7 +554,7 @@ pub async fn cleanup_networks(docker: &Docker) -> Result<(), String> {
|
||||
match CREATED_NETWORKS.try_lock() {
|
||||
Ok(networks) => networks.clone(),
|
||||
Err(_) => {
|
||||
logging::error("Could not acquire network lock for cleanup");
|
||||
wrkflw_logging::error("Could not acquire network lock for cleanup");
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
@@ -545,7 +563,7 @@ pub async fn cleanup_networks(docker: &Docker) -> Result<(), String> {
|
||||
{
|
||||
Ok(networks) => networks,
|
||||
Err(_) => {
|
||||
logging::error("Timeout while trying to get networks for cleanup");
|
||||
wrkflw_logging::error("Timeout while trying to get networks for cleanup");
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
@@ -554,7 +572,7 @@ pub async fn cleanup_networks(docker: &Docker) -> Result<(), String> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Cleaning up {} networks",
|
||||
networks_to_cleanup.len()
|
||||
));
|
||||
@@ -566,9 +584,13 @@ pub async fn cleanup_networks(docker: &Docker) -> Result<(), String> {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(_)) => logging::info(&format!("Successfully removed network: {}", network_id)),
|
||||
Ok(Err(e)) => logging::error(&format!("Error removing network {}: {}", network_id, e)),
|
||||
Err(_) => logging::warning(&format!("Timeout removing network: {}", network_id)),
|
||||
Ok(Ok(_)) => {
|
||||
wrkflw_logging::info(&format!("Successfully removed 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
|
||||
@@ -599,7 +621,7 @@ pub async fn create_job_network(docker: &Docker) -> Result<String, ContainerErro
|
||||
})?;
|
||||
|
||||
track_network(&network_id);
|
||||
logging::info(&format!("Created Docker network: {}", network_id));
|
||||
wrkflw_logging::info(&format!("Created Docker network: {}", network_id));
|
||||
|
||||
Ok(network_id)
|
||||
}
|
||||
@@ -615,7 +637,7 @@ impl ContainerRuntime for DockerRuntime {
|
||||
volumes: &[(&Path, &Path)],
|
||||
) -> Result<ContainerOutput, ContainerError> {
|
||||
// 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
|
||||
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,
|
||||
Err(_) => {
|
||||
logging::error("Docker operation timed out after 360 seconds");
|
||||
wrkflw_logging::error("Docker operation timed out after 360 seconds");
|
||||
Err(ContainerError::ContainerExecution(
|
||||
"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 {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::warning(&format!(
|
||||
wrkflw_logging::warning(&format!(
|
||||
"Pull of image {} timed out, continuing with existing image",
|
||||
image
|
||||
));
|
||||
@@ -662,7 +684,7 @@ impl ContainerRuntime for DockerRuntime {
|
||||
{
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::error(&format!(
|
||||
wrkflw_logging::error(&format!(
|
||||
"Building image {} timed out after 120 seconds",
|
||||
tag
|
||||
));
|
||||
@@ -836,9 +858,9 @@ impl DockerRuntime {
|
||||
// Convert command vector to Vec<String>
|
||||
let cmd_vec: Vec<String> = cmd.iter().map(|&s| s.to_string()).collect();
|
||||
|
||||
logging::debug(&format!("Running command in Docker: {:?}", cmd_vec));
|
||||
logging::debug(&format!("Environment: {:?}", env));
|
||||
logging::debug(&format!("Working directory: {}", working_dir.display()));
|
||||
wrkflw_logging::debug(&format!("Running command in Docker: {:?}", cmd_vec));
|
||||
wrkflw_logging::debug(&format!("Environment: {:?}", env));
|
||||
wrkflw_logging::debug(&format!("Working directory: {}", working_dir.display()));
|
||||
|
||||
// Determine platform-specific configurations
|
||||
let is_windows_image = image.contains("windows")
|
||||
@@ -973,7 +995,7 @@ impl DockerRuntime {
|
||||
_ => -1,
|
||||
},
|
||||
Err(_) => {
|
||||
logging::warning("Container wait operation timed out, treating as failure");
|
||||
wrkflw_logging::warning("Container wait operation timed out, treating as failure");
|
||||
-1
|
||||
}
|
||||
};
|
||||
@@ -1003,7 +1025,7 @@ impl DockerRuntime {
|
||||
}
|
||||
}
|
||||
} 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
|
||||
@@ -1016,7 +1038,7 @@ impl DockerRuntime {
|
||||
untrack_container(&container.id);
|
||||
} else {
|
||||
// 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.",
|
||||
container.id, exit_code, container.id
|
||||
));
|
||||
@@ -1026,13 +1048,13 @@ impl DockerRuntime {
|
||||
|
||||
// Log detailed information about the command execution for debugging
|
||||
if exit_code != 0 {
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Docker command failed with exit code: {}",
|
||||
exit_code
|
||||
));
|
||||
logging::debug(&format!("Failed command: {:?}", cmd));
|
||||
logging::debug(&format!("Working directory: {}", working_dir.display()));
|
||||
logging::debug(&format!("STDERR: {}", stderr));
|
||||
wrkflw_logging::debug(&format!("Failed command: {:?}", cmd));
|
||||
wrkflw_logging::debug(&format!("Working directory: {}", working_dir.display()));
|
||||
wrkflw_logging::debug(&format!("STDERR: {}", stderr));
|
||||
}
|
||||
|
||||
Ok(ContainerOutput {
|
||||
|
||||
@@ -13,13 +13,13 @@ use crate::dependency;
|
||||
use crate::docker;
|
||||
use crate::environment;
|
||||
use crate::podman;
|
||||
use logging;
|
||||
use matrix::MatrixCombination;
|
||||
use models::gitlab::Pipeline;
|
||||
use parser::gitlab::{self, parse_pipeline};
|
||||
use parser::workflow::{self, parse_workflow, ActionInfo, Job, WorkflowDefinition};
|
||||
use runtime::container::ContainerRuntime;
|
||||
use runtime::emulation;
|
||||
use wrkflw_logging;
|
||||
use wrkflw_matrix::MatrixCombination;
|
||||
use wrkflw_models::gitlab::Pipeline;
|
||||
use wrkflw_parser::gitlab::{self, parse_pipeline};
|
||||
use wrkflw_parser::workflow::{self, parse_workflow, ActionInfo, Job, WorkflowDefinition};
|
||||
use wrkflw_runtime::container::ContainerRuntime;
|
||||
use wrkflw_runtime::emulation;
|
||||
|
||||
#[allow(unused_variables, unused_assignments)]
|
||||
/// Execute a GitHub Actions workflow file locally
|
||||
@@ -27,8 +27,8 @@ pub async fn execute_workflow(
|
||||
workflow_path: &Path,
|
||||
config: ExecutionConfig,
|
||||
) -> Result<ExecutionResult, ExecutionError> {
|
||||
logging::info(&format!("Executing workflow: {}", workflow_path.display()));
|
||||
logging::info(&format!("Runtime: {:?}", config.runtime_type));
|
||||
wrkflw_logging::info(&format!("Executing workflow: {}", workflow_path.display()));
|
||||
wrkflw_logging::info(&format!("Runtime: {:?}", config.runtime_type));
|
||||
|
||||
// Determine if this is a GitLab CI/CD pipeline or GitHub Actions workflow
|
||||
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 has_failures {
|
||||
logging::error(&format!("Workflow execution failed:{}", failure_details));
|
||||
wrkflw_logging::error(&format!("Workflow execution failed:{}", failure_details));
|
||||
}
|
||||
|
||||
Ok(ExecutionResult {
|
||||
@@ -168,7 +168,7 @@ async fn execute_gitlab_pipeline(
|
||||
pipeline_path: &Path,
|
||||
config: ExecutionConfig,
|
||||
) -> Result<ExecutionResult, ExecutionError> {
|
||||
logging::info("Executing GitLab CI/CD pipeline");
|
||||
wrkflw_logging::info("Executing GitLab CI/CD pipeline");
|
||||
|
||||
// 1. Parse the GitLab pipeline file
|
||||
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 has_failures {
|
||||
logging::error(&format!("Pipeline execution failed:{}", failure_details));
|
||||
wrkflw_logging::error(&format!("Pipeline execution failed:{}", failure_details));
|
||||
}
|
||||
|
||||
Ok(ExecutionResult {
|
||||
@@ -369,7 +369,7 @@ fn initialize_runtime(
|
||||
match docker::DockerRuntime::new_with_config(preserve_containers_on_failure) {
|
||||
Ok(docker_runtime) => Ok(Box::new(docker_runtime)),
|
||||
Err(e) => {
|
||||
logging::error(&format!(
|
||||
wrkflw_logging::error(&format!(
|
||||
"Failed to initialize Docker runtime: {}, falling back to emulation mode",
|
||||
e
|
||||
));
|
||||
@@ -377,7 +377,7 @@ fn initialize_runtime(
|
||||
}
|
||||
}
|
||||
} 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()))
|
||||
}
|
||||
}
|
||||
@@ -387,7 +387,7 @@ fn initialize_runtime(
|
||||
match podman::PodmanRuntime::new_with_config(preserve_containers_on_failure) {
|
||||
Ok(podman_runtime) => Ok(Box::new(podman_runtime)),
|
||||
Err(e) => {
|
||||
logging::error(&format!(
|
||||
wrkflw_logging::error(&format!(
|
||||
"Failed to initialize Podman runtime: {}, falling back to emulation mode",
|
||||
e
|
||||
));
|
||||
@@ -395,7 +395,7 @@ fn initialize_runtime(
|
||||
}
|
||||
}
|
||||
} 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()))
|
||||
}
|
||||
}
|
||||
@@ -577,7 +577,7 @@ async fn execute_job_with_matrix(
|
||||
if let Some(if_condition) = &job.if_condition {
|
||||
let should_run = evaluate_job_condition(if_condition, env_context, workflow);
|
||||
if !should_run {
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"⏭️ Skipping job '{}' due to condition: {}",
|
||||
job_name, if_condition
|
||||
));
|
||||
@@ -594,11 +594,11 @@ async fn execute_job_with_matrix(
|
||||
// Check if this is a matrix job
|
||||
if let Some(matrix_config) = &job.matrix {
|
||||
// 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)))?;
|
||||
|
||||
if combinations.is_empty() {
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Matrix job '{}' has no valid combinations",
|
||||
job_name
|
||||
));
|
||||
@@ -606,7 +606,7 @@ async fn execute_job_with_matrix(
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Matrix job '{}' expanded to {} combinations",
|
||||
job_name,
|
||||
combinations.len()
|
||||
@@ -674,13 +674,13 @@ async fn execute_job(ctx: JobExecutionContext<'_>) -> Result<JobResult, Executio
|
||||
})?;
|
||||
|
||||
// Copy project files to the job workspace directory
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Copying project files to job workspace: {}",
|
||||
job_dir.path().display()
|
||||
));
|
||||
copy_directory_contents(¤t_dir, job_dir.path())?;
|
||||
|
||||
logging::info(&format!("Executing job: {}", ctx.job_name));
|
||||
wrkflw_logging::info(&format!("Executing job: {}", ctx.job_name));
|
||||
|
||||
let mut job_success = true;
|
||||
|
||||
@@ -780,7 +780,8 @@ async fn execute_matrix_combinations(
|
||||
if ctx.fail_fast && any_failed {
|
||||
// Add skipped results for remaining combinations
|
||||
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 {
|
||||
name: combination_name,
|
||||
status: JobStatus::Skipped,
|
||||
@@ -818,7 +819,7 @@ async fn execute_matrix_combinations(
|
||||
Err(e) => {
|
||||
// On error, mark as failed and continue if not fail-fast
|
||||
any_failed = true;
|
||||
logging::error(&format!("Matrix job failed: {}", e));
|
||||
wrkflw_logging::error(&format!("Matrix job failed: {}", e));
|
||||
|
||||
if ctx.fail_fast {
|
||||
return Err(e);
|
||||
@@ -842,9 +843,9 @@ async fn execute_matrix_job(
|
||||
verbose: bool,
|
||||
) -> Result<JobResult, ExecutionError> {
|
||||
// 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
|
||||
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
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Copying project files to job workspace: {}",
|
||||
job_dir.path().display()
|
||||
));
|
||||
copy_directory_contents(¤t_dir, job_dir.path())?;
|
||||
|
||||
let job_success = if job_template.steps.is_empty() {
|
||||
logging::warning(&format!("Job '{}' has no steps", matrix_job_name));
|
||||
wrkflw_logging::warning(&format!("Job '{}' has no steps", matrix_job_name));
|
||||
true
|
||||
} else {
|
||||
// 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));
|
||||
|
||||
if ctx.verbose {
|
||||
logging::info(&format!(" Executing step: {}", step_name));
|
||||
wrkflw_logging::info(&format!(" Executing step: {}", step_name));
|
||||
}
|
||||
|
||||
// Prepare step environment
|
||||
@@ -1073,7 +1074,9 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
|
||||
// Special handling for Rust actions
|
||||
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
|
||||
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())
|
||||
.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 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())
|
||||
.unwrap_or_else(|_| "not found".to_string());
|
||||
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"🔄 Using system Rust/Cargo: {}",
|
||||
cargo_version.trim()
|
||||
));
|
||||
@@ -1109,7 +1115,10 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
// Get the command from the 'with' parameters
|
||||
if let Some(with_params) = &ctx.step.with {
|
||||
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
|
||||
let mut real_command = format!("cargo {}", command);
|
||||
@@ -1119,7 +1128,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
if !args.is_empty() {
|
||||
// Resolve GitHub-style variables in args
|
||||
let resolved_args = if args.contains("${{") {
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"🔄 Resolving workflow variables in: {}",
|
||||
args
|
||||
));
|
||||
@@ -1133,7 +1142,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
let re_pattern =
|
||||
regex::Regex::new(r"\$\{\{\s*([^}]+)\s*\}\}")
|
||||
.unwrap_or_else(|_| {
|
||||
logging::error(
|
||||
wrkflw_logging::error(
|
||||
"Failed to create regex pattern",
|
||||
);
|
||||
regex::Regex::new(r"\$\{\{.*?\}\}").unwrap()
|
||||
@@ -1141,7 +1150,10 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
|
||||
let resolved =
|
||||
re_pattern.replace_all(&resolved, "").to_string();
|
||||
logging::info(&format!("🔄 Resolved to: {}", resolved));
|
||||
wrkflw_logging::info(&format!(
|
||||
"🔄 Resolved to: {}",
|
||||
resolved
|
||||
));
|
||||
|
||||
resolved.trim().to_string()
|
||||
} else {
|
||||
@@ -1157,7 +1169,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
}
|
||||
}
|
||||
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"🔄 Running actual command: {}",
|
||||
real_command
|
||||
));
|
||||
@@ -1239,13 +1251,13 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "not set".to_string());
|
||||
|
||||
logging::debug(&format!(
|
||||
wrkflw_logging::debug(&format!(
|
||||
"WRKFLW_HIDE_ACTION_MESSAGES value: {}",
|
||||
hide_action_value
|
||||
));
|
||||
|
||||
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
|
||||
if !hide_messages {
|
||||
@@ -1262,7 +1274,10 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
// Common GitHub action pattern: has a 'command' parameter
|
||||
if let Some(cmd) = with_params.get("command") {
|
||||
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
|
||||
@@ -1302,7 +1317,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
if !args.is_empty() {
|
||||
// Resolve GitHub-style variables in args
|
||||
let resolved_args = if args.contains("${{") {
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"🔄 Resolving workflow variables in: {}",
|
||||
args
|
||||
));
|
||||
@@ -1315,7 +1330,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
let re_pattern =
|
||||
regex::Regex::new(r"\$\{\{\s*([^}]+)\s*\}\}")
|
||||
.unwrap_or_else(|_| {
|
||||
logging::error(
|
||||
wrkflw_logging::error(
|
||||
"Failed to create regex pattern",
|
||||
);
|
||||
regex::Regex::new(r"\$\{\{.*?\}\}").unwrap()
|
||||
@@ -1323,7 +1338,10 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
|
||||
let resolved =
|
||||
re_pattern.replace_all(&resolved, "").to_string();
|
||||
logging::info(&format!("🔄 Resolved to: {}", resolved));
|
||||
wrkflw_logging::info(&format!(
|
||||
"🔄 Resolved to: {}",
|
||||
resolved
|
||||
));
|
||||
|
||||
resolved.trim().to_string()
|
||||
} else {
|
||||
@@ -1342,7 +1360,10 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
if should_run_real_command && !real_command_parts.is_empty() {
|
||||
// Build a final command string
|
||||
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
|
||||
cmd.clear();
|
||||
@@ -1737,7 +1758,7 @@ async fn prepare_runner_image(
|
||||
) -> Result<(), ExecutionError> {
|
||||
// Try to pull the image first
|
||||
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
|
||||
@@ -1750,7 +1771,7 @@ async fn prepare_runner_image(
|
||||
.map_err(|e| ExecutionError::Runtime(e.to_string()))
|
||||
{
|
||||
if verbose {
|
||||
logging::info(&format!("Using customized image: {}", custom_image));
|
||||
wrkflw_logging::info(&format!("Using customized image: {}", custom_image));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@@ -2028,7 +2049,7 @@ fn evaluate_job_condition(
|
||||
env_context: &HashMap<String, String>,
|
||||
workflow: &WorkflowDefinition,
|
||||
) -> bool {
|
||||
logging::debug(&format!("Evaluating condition: {}", condition));
|
||||
wrkflw_logging::debug(&format!("Evaluating condition: {}", condition));
|
||||
|
||||
// For now, implement basic pattern matching for common conditions
|
||||
// TODO: Implement a full GitHub Actions expression evaluator
|
||||
@@ -2051,14 +2072,14 @@ fn evaluate_job_condition(
|
||||
if condition.contains("needs.") && condition.contains(".outputs.") {
|
||||
// For now, simulate that outputs are available but empty
|
||||
// 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",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default to true for unknown conditions to avoid breaking workflows
|
||||
logging::warning(&format!(
|
||||
wrkflw_logging::warning(&format!(
|
||||
"Unknown condition pattern: '{}' - defaulting to true",
|
||||
condition
|
||||
));
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use chrono::Utc;
|
||||
use matrix::MatrixCombination;
|
||||
use parser::workflow::WorkflowDefinition;
|
||||
use serde_yaml::Value;
|
||||
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<()> {
|
||||
// Create necessary directories
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use async_trait::async_trait;
|
||||
use logging;
|
||||
use once_cell::sync::Lazy;
|
||||
use runtime::container::{ContainerError, ContainerOutput, ContainerRuntime};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::process::Stdio;
|
||||
use std::sync::Mutex;
|
||||
use tempfile;
|
||||
use tokio::process::Command;
|
||||
use utils;
|
||||
use utils::fd;
|
||||
use wrkflw_logging;
|
||||
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()));
|
||||
// Map to track customized images for a job
|
||||
@@ -46,7 +46,7 @@ impl PodmanRuntime {
|
||||
match CUSTOMIZED_IMAGES.lock() {
|
||||
Ok(images) => images.get(&key).cloned(),
|
||||
Err(e) => {
|
||||
logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ impl PodmanRuntime {
|
||||
if let Err(e) = CUSTOMIZED_IMAGES.lock().map(|mut images| {
|
||||
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() {
|
||||
Ok(keys) => keys,
|
||||
Err(e) => {
|
||||
logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
return None;
|
||||
}
|
||||
};
|
||||
@@ -103,7 +103,7 @@ impl PodmanRuntime {
|
||||
match CUSTOMIZED_IMAGES.lock() {
|
||||
Ok(images) => images.get(&key).cloned(),
|
||||
Err(e) => {
|
||||
logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
wrkflw_logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@ impl PodmanRuntime {
|
||||
if let Err(e) = CUSTOMIZED_IMAGES.lock().map(|mut images| {
|
||||
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());
|
||||
|
||||
logging::debug(&format!(
|
||||
wrkflw_logging::debug(&format!(
|
||||
"Running Podman command: podman {}",
|
||||
args.join(" ")
|
||||
));
|
||||
@@ -192,7 +192,7 @@ impl PodmanRuntime {
|
||||
match result {
|
||||
Ok(output) => output,
|
||||
Err(_) => {
|
||||
logging::error("Podman operation timed out after 360 seconds");
|
||||
wrkflw_logging::error("Podman operation timed out after 360 seconds");
|
||||
Err(ContainerError::ContainerExecution(
|
||||
"Operation timed out".to_string(),
|
||||
))
|
||||
@@ -244,7 +244,7 @@ pub fn is_available() -> bool {
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
logging::debug("Podman CLI is not available");
|
||||
wrkflw_logging::debug("Podman CLI is not available");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -257,7 +257,7 @@ pub fn is_available() -> bool {
|
||||
{
|
||||
Ok(rt) => rt,
|
||||
Err(e) => {
|
||||
logging::error(&format!(
|
||||
wrkflw_logging::error(&format!(
|
||||
"Failed to create runtime for Podman availability check: {}",
|
||||
e
|
||||
));
|
||||
@@ -278,16 +278,16 @@ pub fn is_available() -> bool {
|
||||
if output.status.success() {
|
||||
true
|
||||
} else {
|
||||
logging::debug("Podman info command failed");
|
||||
wrkflw_logging::debug("Podman info command failed");
|
||||
false
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
logging::debug(&format!("Podman info command error: {}", e));
|
||||
wrkflw_logging::debug(&format!("Podman info command error: {}", e));
|
||||
false
|
||||
}
|
||||
Err(_) => {
|
||||
logging::debug("Podman info command timed out after 1 second");
|
||||
wrkflw_logging::debug("Podman info command timed out after 1 second");
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -296,7 +296,7 @@ pub fn is_available() -> bool {
|
||||
{
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::debug("Podman availability check timed out");
|
||||
wrkflw_logging::debug("Podman availability check timed out");
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -304,7 +304,9 @@ pub fn is_available() -> bool {
|
||||
}) {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::debug("Failed to redirect stderr when checking Podman availability");
|
||||
wrkflw_logging::debug(
|
||||
"Failed to redirect stderr when checking Podman availability",
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -318,7 +320,7 @@ pub fn is_available() -> bool {
|
||||
return match handle.join() {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::warning("Podman availability check thread panicked");
|
||||
wrkflw_logging::warning("Podman availability check thread panicked");
|
||||
false
|
||||
}
|
||||
};
|
||||
@@ -326,7 +328,9 @@ pub fn is_available() -> bool {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -352,12 +356,12 @@ pub async fn cleanup_resources() {
|
||||
match tokio::time::timeout(cleanup_timeout, cleanup_containers()).await {
|
||||
Ok(result) => {
|
||||
if let Err(e) = result {
|
||||
logging::error(&format!("Error during container cleanup: {}", e));
|
||||
wrkflw_logging::error(&format!("Error during container cleanup: {}", e));
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
logging::warning("Podman cleanup timed out, some resources may not have been removed")
|
||||
}
|
||||
Err(_) => wrkflw_logging::warning(
|
||||
"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() {
|
||||
Ok(containers) => containers.clone(),
|
||||
Err(_) => {
|
||||
logging::error("Could not acquire container lock for cleanup");
|
||||
wrkflw_logging::error("Could not acquire container lock for cleanup");
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
@@ -378,7 +382,7 @@ pub async fn cleanup_containers() -> Result<(), String> {
|
||||
{
|
||||
Ok(containers) => containers,
|
||||
Err(_) => {
|
||||
logging::error("Timeout while trying to get containers for cleanup");
|
||||
wrkflw_logging::error("Timeout while trying to get containers for cleanup");
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
@@ -387,7 +391,7 @@ pub async fn cleanup_containers() -> Result<(), String> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Cleaning up {} containers",
|
||||
containers_to_cleanup.len()
|
||||
));
|
||||
@@ -408,15 +412,18 @@ pub async fn cleanup_containers() -> Result<(), String> {
|
||||
match stop_result {
|
||||
Ok(Ok(output)) => {
|
||||
if output.status.success() {
|
||||
logging::debug(&format!("Stopped container: {}", container_id));
|
||||
wrkflw_logging::debug(&format!("Stopped container: {}", container_id));
|
||||
} else {
|
||||
logging::warning(&format!("Error stopping container {}", container_id));
|
||||
wrkflw_logging::warning(&format!("Error stopping container {}", container_id));
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
logging::warning(&format!("Error stopping container {}: {}", container_id, e))
|
||||
Ok(Err(e)) => wrkflw_logging::warning(&format!(
|
||||
"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
|
||||
@@ -433,15 +440,18 @@ pub async fn cleanup_containers() -> Result<(), String> {
|
||||
match remove_result {
|
||||
Ok(Ok(output)) => {
|
||||
if output.status.success() {
|
||||
logging::debug(&format!("Removed container: {}", container_id));
|
||||
wrkflw_logging::debug(&format!("Removed container: {}", container_id));
|
||||
} else {
|
||||
logging::warning(&format!("Error removing container {}", container_id));
|
||||
wrkflw_logging::warning(&format!("Error removing container {}", container_id));
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
logging::warning(&format!("Error removing container {}: {}", container_id, e))
|
||||
Ok(Err(e)) => wrkflw_logging::warning(&format!(
|
||||
"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
|
||||
@@ -462,7 +472,7 @@ impl ContainerRuntime for PodmanRuntime {
|
||||
volumes: &[(&Path, &Path)],
|
||||
) -> Result<ContainerOutput, ContainerError> {
|
||||
// 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
|
||||
|
||||
@@ -475,7 +485,7 @@ impl ContainerRuntime for PodmanRuntime {
|
||||
{
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::error("Podman operation timed out after 360 seconds");
|
||||
wrkflw_logging::error("Podman operation timed out after 360 seconds");
|
||||
Err(ContainerError::ContainerExecution(
|
||||
"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 {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::warning(&format!(
|
||||
wrkflw_logging::warning(&format!(
|
||||
"Pull of image {} timed out, continuing with existing image",
|
||||
image
|
||||
));
|
||||
@@ -508,7 +518,7 @@ impl ContainerRuntime for PodmanRuntime {
|
||||
{
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::error(&format!(
|
||||
wrkflw_logging::error(&format!(
|
||||
"Building image {} timed out after 120 seconds",
|
||||
tag
|
||||
));
|
||||
@@ -664,9 +674,9 @@ impl PodmanRuntime {
|
||||
working_dir: &Path,
|
||||
volumes: &[(&Path, &Path)],
|
||||
) -> Result<ContainerOutput, ContainerError> {
|
||||
logging::debug(&format!("Running command in Podman: {:?}", cmd));
|
||||
logging::debug(&format!("Environment: {:?}", env_vars));
|
||||
logging::debug(&format!("Working directory: {}", working_dir.display()));
|
||||
wrkflw_logging::debug(&format!("Running command in Podman: {:?}", cmd));
|
||||
wrkflw_logging::debug(&format!("Environment: {:?}", env_vars));
|
||||
wrkflw_logging::debug(&format!("Working directory: {}", working_dir.display()));
|
||||
|
||||
// Generate a unique container name
|
||||
let container_name = format!("wrkflw-{}", uuid::Uuid::new_v4());
|
||||
@@ -742,13 +752,13 @@ impl PodmanRuntime {
|
||||
match cleanup_result {
|
||||
Ok(Ok(cleanup_output)) => {
|
||||
if !cleanup_output.status.success() {
|
||||
logging::debug(&format!(
|
||||
wrkflw_logging::debug(&format!(
|
||||
"Failed to remove successful container {}",
|
||||
container_name
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => logging::debug(&format!(
|
||||
_ => wrkflw_logging::debug(&format!(
|
||||
"Timeout removing successful container {}",
|
||||
container_name
|
||||
)),
|
||||
@@ -760,7 +770,7 @@ impl PodmanRuntime {
|
||||
// Failed container
|
||||
if self.preserve_containers_on_failure {
|
||||
// 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.",
|
||||
container_name, output.exit_code, container_name
|
||||
));
|
||||
@@ -789,11 +799,11 @@ impl PodmanRuntime {
|
||||
.await;
|
||||
|
||||
match cleanup_result {
|
||||
Ok(Ok(_)) => logging::debug(&format!(
|
||||
Ok(Ok(_)) => wrkflw_logging::debug(&format!(
|
||||
"Cleaned up failed execution container {}",
|
||||
container_name
|
||||
)),
|
||||
_ => logging::debug(&format!(
|
||||
_ => wrkflw_logging::debug(&format!(
|
||||
"Failed to clean up execution failure container {}",
|
||||
container_name
|
||||
)),
|
||||
@@ -806,17 +816,17 @@ impl PodmanRuntime {
|
||||
match &result {
|
||||
Ok(output) => {
|
||||
if output.exit_code != 0 {
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Podman command failed with exit code: {}",
|
||||
output.exit_code
|
||||
));
|
||||
logging::debug(&format!("Failed command: {:?}", cmd));
|
||||
logging::debug(&format!("Working directory: {}", working_dir.display()));
|
||||
logging::debug(&format!("STDERR: {}", output.stderr));
|
||||
wrkflw_logging::debug(&format!("Failed command: {:?}", cmd));
|
||||
wrkflw_logging::debug(&format!("Working directory: {}", working_dir.display()));
|
||||
wrkflw_logging::debug(&format!("STDERR: {}", output.stderr));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
logging::error(&format!("Podman execution error: {}", e));
|
||||
wrkflw_logging::error(&format!("Podman execution error: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
[package]
|
||||
name = "github"
|
||||
name = "wrkflw-github"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "github functionality for wrkflw"
|
||||
description = "GitHub API integration for wrkflw workflow execution engine"
|
||||
license.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Add other crate dependencies as needed
|
||||
models = { path = "../models" }
|
||||
# Internal crates
|
||||
wrkflw-models = { path = "../models", version = "0.6.0" }
|
||||
|
||||
# External dependencies from workspace
|
||||
serde.workspace = true
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
[package]
|
||||
name = "gitlab"
|
||||
name = "wrkflw-gitlab"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "gitlab functionality for wrkflw"
|
||||
description = "GitLab API integration for wrkflw workflow execution engine"
|
||||
license.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
wrkflw-models = { path = "../models", version = "0.6.0" }
|
||||
|
||||
# External dependencies
|
||||
lazy_static.workspace = true
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
[package]
|
||||
name = "logging"
|
||||
name = "wrkflw-logging"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "logging functionality for wrkflw"
|
||||
description = "Logging functionality for wrkflw workflow execution engine"
|
||||
license.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
wrkflw-models = { path = "../models", version = "0.6.0" }
|
||||
|
||||
# External dependencies
|
||||
chrono.workspace = true
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
[package]
|
||||
name = "matrix"
|
||||
name = "wrkflw-matrix"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "matrix functionality for wrkflw"
|
||||
description = "Matrix job parallelization for wrkflw workflow execution engine"
|
||||
license.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
wrkflw-models = { path = "../models", version = "0.6.0" }
|
||||
|
||||
# External dependencies
|
||||
indexmap.workspace = true
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
[package]
|
||||
name = "models"
|
||||
name = "wrkflw-models"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Data models for wrkflw"
|
||||
description = "Data models and structures for wrkflw workflow execution engine"
|
||||
license.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
[package]
|
||||
name = "parser"
|
||||
name = "wrkflw-parser"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Parser functionality for wrkflw"
|
||||
description = "Workflow parsing functionality for wrkflw execution engine"
|
||||
license.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
matrix = { path = "../matrix" }
|
||||
wrkflw-models = { path = "../models", version = "0.6.0" }
|
||||
wrkflw-matrix = { path = "../matrix", version = "0.6.0" }
|
||||
|
||||
# External dependencies
|
||||
jsonschema.workspace = true
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::schema::{SchemaType, SchemaValidator};
|
||||
use crate::workflow;
|
||||
use models::gitlab::Pipeline;
|
||||
use models::ValidationResult;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use thiserror::Error;
|
||||
use wrkflw_models::gitlab::Pipeline;
|
||||
use wrkflw_models::ValidationResult;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum GitlabParserError {
|
||||
@@ -204,8 +204,8 @@ pub fn convert_to_workflow_format(pipeline: &Pipeline) -> workflow::WorkflowDefi
|
||||
for (i, service) in services.iter().enumerate() {
|
||||
let service_name = format!("service-{}", i);
|
||||
let service_image = match service {
|
||||
models::gitlab::Service::Simple(name) => name.clone(),
|
||||
models::gitlab::Service::Detailed { name, .. } => name.clone(),
|
||||
wrkflw_models::gitlab::Service::Simple(name) => name.clone(),
|
||||
wrkflw_models::gitlab::Service::Detailed { name, .. } => name.clone(),
|
||||
};
|
||||
|
||||
let service = workflow::Service {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use matrix::MatrixConfig;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use wrkflw_matrix::MatrixConfig;
|
||||
|
||||
use super::schema::SchemaValidator;
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
[package]
|
||||
name = "runtime"
|
||||
name = "wrkflw-runtime"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Runtime environment for wrkflw"
|
||||
description = "Runtime execution environment for wrkflw workflow engine"
|
||||
license.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
logging = { path = "../logging", version = "0.4.0" }
|
||||
wrkflw-models = { path = "../models", version = "0.6.0" }
|
||||
wrkflw-logging = { path = "../logging", version = "0.6.0" }
|
||||
|
||||
# External dependencies
|
||||
async-trait.workspace = true
|
||||
@@ -18,5 +23,5 @@ serde_yaml.workspace = true
|
||||
tempfile = "3.9"
|
||||
tokio.workspace = true
|
||||
futures = "0.3"
|
||||
utils = { path = "../utils", version = "0.4.0" }
|
||||
wrkflw-utils = { path = "../utils", version = "0.6.0" }
|
||||
which = "4.4"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::container::{ContainerError, ContainerOutput, ContainerRuntime};
|
||||
use async_trait::async_trait;
|
||||
use logging;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
@@ -9,6 +8,7 @@ use std::process::Command;
|
||||
use std::sync::Mutex;
|
||||
use tempfile::TempDir;
|
||||
use which;
|
||||
use wrkflw_logging;
|
||||
|
||||
// Global collection of resources to clean up
|
||||
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
|
||||
logging::info(&format!("Executing command in container: {}", command_str));
|
||||
logging::info(&format!("Working directory: {}", working_dir.display()));
|
||||
logging::info(&format!("Command length: {}", command.len()));
|
||||
wrkflw_logging::info(&format!("Executing command in container: {}", command_str));
|
||||
wrkflw_logging::info(&format!("Working directory: {}", working_dir.display()));
|
||||
wrkflw_logging::info(&format!("Command length: {}", command.len()));
|
||||
|
||||
if command.is_empty() {
|
||||
return Err(ContainerError::ContainerExecution(
|
||||
@@ -174,13 +174,13 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
|
||||
// Print each command part separately for debugging
|
||||
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
|
||||
logging::info("Environment variables:");
|
||||
wrkflw_logging::info("Environment variables:");
|
||||
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
|
||||
@@ -197,7 +197,7 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
// If found, use that as the working directory
|
||||
if let Some(path) = workspace_path {
|
||||
if path.exists() {
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Using environment-defined workspace: {}",
|
||||
path.display()
|
||||
));
|
||||
@@ -206,7 +206,7 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
// Fallback to current directory
|
||||
let current_dir =
|
||||
std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Using current directory: {}",
|
||||
current_dir.display()
|
||||
));
|
||||
@@ -215,7 +215,7 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
} else {
|
||||
// Fallback to current directory
|
||||
let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Using current directory: {}",
|
||||
current_dir.display()
|
||||
));
|
||||
@@ -225,7 +225,7 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
working_dir.to_path_buf()
|
||||
};
|
||||
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Using actual working directory: {}",
|
||||
actual_working_dir.display()
|
||||
));
|
||||
@@ -233,8 +233,8 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
// Check if path contains the command (for shell script execution)
|
||||
let command_path = which::which(command[0]);
|
||||
match &command_path {
|
||||
Ok(path) => logging::info(&format!("Found command at: {}", path.display())),
|
||||
Err(e) => logging::error(&format!(
|
||||
Ok(path) => wrkflw_logging::info(&format!("Found command at: {}", path.display())),
|
||||
Err(e) => wrkflw_logging::error(&format!(
|
||||
"Command not found in PATH: {} - Error: {}",
|
||||
command[0], e
|
||||
)),
|
||||
@@ -246,7 +246,7 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
|| command_str.starts_with("mkdir ")
|
||||
|| command_str.starts_with("mv ")
|
||||
{
|
||||
logging::info("Executing as shell command");
|
||||
wrkflw_logging::info("Executing as shell command");
|
||||
// Execute as a shell command
|
||||
let mut cmd = Command::new("sh");
|
||||
cmd.arg("-c");
|
||||
@@ -264,7 +264,7 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
let output = String::from_utf8_lossy(&output_result.stdout).to_string();
|
||||
let error = String::from_utf8_lossy(&output_result.stderr).to_string();
|
||||
|
||||
logging::debug(&format!(
|
||||
wrkflw_logging::debug(&format!(
|
||||
"Shell command completed with 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
|
||||
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: {}",
|
||||
current_dir.display()
|
||||
));
|
||||
@@ -326,7 +326,7 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
if *key == "CARGO_HOME" && value.contains("${CI_PROJECT_DIR}") {
|
||||
let cargo_home =
|
||||
value.replace("${CI_PROJECT_DIR}", ¤t_dir.to_string_lossy());
|
||||
logging::info(&format!("Setting CARGO_HOME to: {}", cargo_home));
|
||||
wrkflw_logging::info(&format!("Setting CARGO_HOME to: {}", cargo_home));
|
||||
cmd.env(key, cargo_home);
|
||||
} else {
|
||||
cmd.env(key, value);
|
||||
@@ -338,7 +338,7 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
cmd.args(&parts[1..]);
|
||||
}
|
||||
|
||||
logging::debug(&format!(
|
||||
wrkflw_logging::debug(&format!(
|
||||
"Executing Rust command: {} in {}",
|
||||
command_str,
|
||||
current_dir.display()
|
||||
@@ -350,7 +350,7 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
let output = String::from_utf8_lossy(&output_result.stdout).to_string();
|
||||
let error = String::from_utf8_lossy(&output_result.stderr).to_string();
|
||||
|
||||
logging::debug(&format!("Command exit code: {}", exit_code));
|
||||
wrkflw_logging::debug(&format!("Command exit code: {}", exit_code));
|
||||
|
||||
if exit_code != 0 {
|
||||
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 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 {
|
||||
let mut error_details = format!(
|
||||
@@ -443,12 +443,12 @@ impl ContainerRuntime for EmulationRuntime {
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
async fn build_image(&self, dockerfile: &Path, tag: &str) -> Result<(), ContainerError> {
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"🔄 Emulation: Pretending to build image {} from {}",
|
||||
tag,
|
||||
dockerfile.display()
|
||||
@@ -543,14 +543,14 @@ pub async fn handle_special_action(action: &str) -> Result<(), ContainerError> {
|
||||
"latest"
|
||||
};
|
||||
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"🔄 Processing action: {} @ {}",
|
||||
action_name, action_version
|
||||
));
|
||||
|
||||
// Handle specific known actions with special requirements
|
||||
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
|
||||
let nix_installed = Command::new("which")
|
||||
@@ -560,56 +560,56 @@ pub async fn handle_special_action(action: &str) -> Result<(), ContainerError> {
|
||||
.unwrap_or(false);
|
||||
|
||||
if !nix_installed {
|
||||
logging::info("🔄 Emulation: Nix is required but not installed.");
|
||||
logging::info(
|
||||
wrkflw_logging::info("🔄 Emulation: Nix is required but not installed.");
|
||||
wrkflw_logging::info(
|
||||
"🔄 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 {
|
||||
logging::info("🔄 Emulation: Using system-installed Nix");
|
||||
wrkflw_logging::info("🔄 Emulation: Using system-installed Nix");
|
||||
}
|
||||
} else if action.starts_with("actions-rs/cargo@") {
|
||||
// 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
|
||||
check_command_available("cargo", "Rust/Cargo", "https://rustup.rs/");
|
||||
} else if action.starts_with("actions-rs/toolchain@") {
|
||||
// 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/");
|
||||
} else if action.starts_with("actions-rs/fmt@") {
|
||||
// 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");
|
||||
} else if action.starts_with("actions/setup-node@") {
|
||||
// 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/");
|
||||
} else if action.starts_with("actions/setup-python@") {
|
||||
// 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/");
|
||||
} else if action.starts_with("actions/setup-java@") {
|
||||
// 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/");
|
||||
} else if action.starts_with("actions/checkout@") {
|
||||
// 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@") {
|
||||
// Cache action - can't really emulate caching effectively
|
||||
logging::info(
|
||||
wrkflw_logging::info(
|
||||
"🔄 Detected cache action - caching is not fully supported in emulation mode",
|
||||
);
|
||||
} else {
|
||||
// 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_name
|
||||
));
|
||||
@@ -628,12 +628,12 @@ fn check_command_available(command: &str, name: &str, install_url: &str) {
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_available {
|
||||
logging::warning(&format!("{} is required but not found on the system", name));
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::warning(&format!("{} is required but not found on the system", name));
|
||||
wrkflw_logging::info(&format!(
|
||||
"To use this action, please install {}: {}",
|
||||
name, install_url
|
||||
));
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Continuing emulation, but {} commands will fail",
|
||||
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 output.status.success() {
|
||||
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 {
|
||||
logging::info(&format!("Cleaning up emulated process: {}", pid));
|
||||
wrkflw_logging::info(&format!("Cleaning up emulated process: {}", pid));
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
@@ -747,7 +747,7 @@ async fn cleanup_workspaces() {
|
||||
};
|
||||
|
||||
for workspace_path in workspaces_to_cleanup {
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Cleaning up emulation workspace: {}",
|
||||
workspace_path.display()
|
||||
));
|
||||
@@ -755,8 +755,8 @@ async fn cleanup_workspaces() {
|
||||
// Only attempt to remove if it exists
|
||||
if workspace_path.exists() {
|
||||
match fs::remove_dir_all(&workspace_path) {
|
||||
Ok(_) => logging::info("Successfully removed workspace directory"),
|
||||
Err(e) => logging::error(&format!("Error removing workspace: {}", e)),
|
||||
Ok(_) => wrkflw_logging::info("Successfully removed workspace directory"),
|
||||
Err(e) => wrkflw_logging::error(&format!("Error removing workspace: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
[package]
|
||||
name = "ui"
|
||||
name = "wrkflw-ui"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "user interface functionality for wrkflw"
|
||||
description = "Terminal user interface for wrkflw workflow execution engine"
|
||||
license.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
evaluator = { path = "../evaluator" }
|
||||
executor = { path = "../executor" }
|
||||
logging = { path = "../logging" }
|
||||
utils = { path = "../utils" }
|
||||
github = { path = "../github" }
|
||||
wrkflw-models = { path = "../models", version = "0.6.0" }
|
||||
wrkflw-evaluator = { path = "../evaluator", version = "0.6.0" }
|
||||
wrkflw-executor = { path = "../executor", version = "0.6.0" }
|
||||
wrkflw-logging = { path = "../logging", version = "0.6.0" }
|
||||
wrkflw-utils = { path = "../utils", version = "0.6.0" }
|
||||
wrkflw-github = { path = "../github", version = "0.6.0" }
|
||||
|
||||
# External dependencies
|
||||
chrono.workspace = true
|
||||
|
||||
@@ -11,12 +11,12 @@ use crossterm::{
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use executor::RuntimeType;
|
||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||
use std::io::{self, stdout};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc;
|
||||
use std::time::{Duration, Instant};
|
||||
use wrkflw_executor::RuntimeType;
|
||||
|
||||
pub use state::App;
|
||||
|
||||
@@ -50,7 +50,7 @@ pub async fn run_wrkflw_tui(
|
||||
|
||||
if app.validation_mode {
|
||||
app.logs.push("Starting in validation mode".to_string());
|
||||
logging::info("Starting in validation mode");
|
||||
wrkflw_logging::info("Starting in validation mode");
|
||||
}
|
||||
|
||||
// Load workflows
|
||||
@@ -108,13 +108,13 @@ pub async fn run_wrkflw_tui(
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
// 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
|
||||
// For other commands, return the error
|
||||
if let Some(path) = path {
|
||||
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)
|
||||
.await
|
||||
} else if path.is_dir() {
|
||||
@@ -273,7 +273,7 @@ fn run_tui_event_loop(
|
||||
"[{}] DEBUG: Shift+r detected - this should be uppercase R",
|
||||
timestamp
|
||||
));
|
||||
logging::info(
|
||||
wrkflw_logging::info(
|
||||
"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",
|
||||
timestamp
|
||||
));
|
||||
logging::info("Reset key 'Shift+R' pressed");
|
||||
wrkflw_logging::info("Reset key 'Shift+R' pressed");
|
||||
|
||||
if !app.running {
|
||||
// Reset workflow status
|
||||
@@ -367,7 +367,7 @@ fn run_tui_event_loop(
|
||||
"Workflow '{}' is already running",
|
||||
workflow.name
|
||||
));
|
||||
logging::warning(&format!(
|
||||
wrkflw_logging::warning(&format!(
|
||||
"Workflow '{}' is already running",
|
||||
workflow.name
|
||||
));
|
||||
@@ -408,7 +408,7 @@ fn run_tui_event_loop(
|
||||
));
|
||||
}
|
||||
|
||||
logging::warning(&format!(
|
||||
wrkflw_logging::warning(&format!(
|
||||
"Cannot trigger workflow in {} state",
|
||||
status_text
|
||||
));
|
||||
@@ -416,20 +416,22 @@ fn run_tui_event_loop(
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
app.logs.push(
|
||||
"Cannot trigger workflow while another operation is in progress"
|
||||
.to_string(),
|
||||
);
|
||||
logging::warning(
|
||||
wrkflw_logging::warning(
|
||||
"Cannot trigger workflow while another operation is in progress",
|
||||
);
|
||||
} else if app.selected_tab != 0 {
|
||||
app.logs
|
||||
.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
|
||||
app.switch_tab(0);
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ use crate::models::{
|
||||
};
|
||||
use chrono::Local;
|
||||
use crossterm::event::KeyCode;
|
||||
use executor::{JobStatus, RuntimeType, StepStatus};
|
||||
use ratatui::widgets::{ListState, TableState};
|
||||
use std::sync::mpsc;
|
||||
use std::time::{Duration, Instant};
|
||||
use wrkflw_executor::{JobStatus, RuntimeType, StepStatus};
|
||||
|
||||
/// Application state
|
||||
pub struct App {
|
||||
@@ -69,8 +69,10 @@ impl App {
|
||||
// Use a very short timeout to prevent blocking the UI
|
||||
let result = std::thread::scope(|s| {
|
||||
let handle = s.spawn(|| {
|
||||
utils::fd::with_stderr_to_null(executor::docker::is_available)
|
||||
.unwrap_or(false)
|
||||
wrkflw_utils::fd::with_stderr_to_null(
|
||||
wrkflw_executor::docker::is_available,
|
||||
)
|
||||
.unwrap_or(false)
|
||||
});
|
||||
|
||||
// Set a short timeout for the thread
|
||||
@@ -85,7 +87,7 @@ impl App {
|
||||
}
|
||||
|
||||
// If we reach here, the check took too long
|
||||
logging::warning(
|
||||
wrkflw_logging::warning(
|
||||
"Docker availability check timed out, falling back to emulation mode",
|
||||
);
|
||||
false
|
||||
@@ -94,7 +96,7 @@ impl App {
|
||||
}) {
|
||||
Ok(result) => result,
|
||||
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
|
||||
}
|
||||
};
|
||||
@@ -104,12 +106,12 @@ impl App {
|
||||
"Docker is not available or unresponsive. Using emulation mode instead."
|
||||
.to_string(),
|
||||
);
|
||||
logging::warning(
|
||||
wrkflw_logging::warning(
|
||||
"Docker is not available or unresponsive. Using emulation mode instead.",
|
||||
);
|
||||
RuntimeType::Emulation
|
||||
} else {
|
||||
logging::info("Docker is available, using Docker runtime");
|
||||
wrkflw_logging::info("Docker is available, using Docker runtime");
|
||||
RuntimeType::Docker
|
||||
}
|
||||
}
|
||||
@@ -119,8 +121,10 @@ impl App {
|
||||
// Use a very short timeout to prevent blocking the UI
|
||||
let result = std::thread::scope(|s| {
|
||||
let handle = s.spawn(|| {
|
||||
utils::fd::with_stderr_to_null(executor::podman::is_available)
|
||||
.unwrap_or(false)
|
||||
wrkflw_utils::fd::with_stderr_to_null(
|
||||
wrkflw_executor::podman::is_available,
|
||||
)
|
||||
.unwrap_or(false)
|
||||
});
|
||||
|
||||
// Set a short timeout for the thread
|
||||
@@ -135,7 +139,7 @@ impl App {
|
||||
}
|
||||
|
||||
// If we reach here, the check took too long
|
||||
logging::warning(
|
||||
wrkflw_logging::warning(
|
||||
"Podman availability check timed out, falling back to emulation mode",
|
||||
);
|
||||
false
|
||||
@@ -144,7 +148,7 @@ impl App {
|
||||
}) {
|
||||
Ok(result) => result,
|
||||
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
|
||||
}
|
||||
};
|
||||
@@ -154,12 +158,12 @@ impl App {
|
||||
"Podman is not available or unresponsive. Using emulation mode instead."
|
||||
.to_string(),
|
||||
);
|
||||
logging::warning(
|
||||
wrkflw_logging::warning(
|
||||
"Podman is not available or unresponsive. Using emulation mode instead.",
|
||||
);
|
||||
RuntimeType::Emulation
|
||||
} else {
|
||||
logging::info("Podman is available, using Podman runtime");
|
||||
wrkflw_logging::info("Podman is available, using Podman runtime");
|
||||
RuntimeType::Podman
|
||||
}
|
||||
}
|
||||
@@ -227,7 +231,7 @@ impl App {
|
||||
let timestamp = Local::now().format("%H:%M:%S").to_string();
|
||||
self.logs
|
||||
.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 {
|
||||
@@ -445,7 +449,7 @@ impl App {
|
||||
let timestamp = Local::now().format("%H:%M:%S").to_string();
|
||||
self.logs
|
||||
.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(
|
||||
&mut self,
|
||||
workflow_idx: usize,
|
||||
result: Result<(Vec<executor::JobResult>, ()), String>,
|
||||
result: Result<(Vec<wrkflw_executor::JobResult>, ()), String>,
|
||||
) {
|
||||
if workflow_idx >= self.workflows.len() {
|
||||
let timestamp = Local::now().format("%H:%M:%S").to_string();
|
||||
@@ -461,7 +465,7 @@ impl App {
|
||||
"[{}] Error: Invalid workflow index received",
|
||||
timestamp
|
||||
));
|
||||
logging::error("Invalid workflow index received in process_execution_result");
|
||||
wrkflw_logging::error("Invalid workflow index received in process_execution_result");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -490,15 +494,15 @@ impl App {
|
||||
.push(format!("[{}] Operation completed successfully.", timestamp));
|
||||
execution_details.progress = 1.0;
|
||||
|
||||
// Convert executor::JobResult to our JobExecution struct
|
||||
// Convert wrkflw_executor::JobResult to our JobExecution struct
|
||||
execution_details.jobs = jobs
|
||||
.iter()
|
||||
.map(|job_result| JobExecution {
|
||||
name: job_result.name.clone(),
|
||||
status: match job_result.status {
|
||||
executor::JobStatus::Success => JobStatus::Success,
|
||||
executor::JobStatus::Failure => JobStatus::Failure,
|
||||
executor::JobStatus::Skipped => JobStatus::Skipped,
|
||||
wrkflw_executor::JobStatus::Success => JobStatus::Success,
|
||||
wrkflw_executor::JobStatus::Failure => JobStatus::Failure,
|
||||
wrkflw_executor::JobStatus::Skipped => JobStatus::Skipped,
|
||||
},
|
||||
steps: job_result
|
||||
.steps
|
||||
@@ -506,9 +510,9 @@ impl App {
|
||||
.map(|step_result| StepExecution {
|
||||
name: step_result.name.clone(),
|
||||
status: match step_result.status {
|
||||
executor::StepStatus::Success => StepStatus::Success,
|
||||
executor::StepStatus::Failure => StepStatus::Failure,
|
||||
executor::StepStatus::Skipped => StepStatus::Skipped,
|
||||
wrkflw_executor::StepStatus::Success => StepStatus::Success,
|
||||
wrkflw_executor::StepStatus::Failure => StepStatus::Failure,
|
||||
wrkflw_executor::StepStatus::Skipped => StepStatus::Skipped,
|
||||
},
|
||||
output: step_result.output.clone(),
|
||||
})
|
||||
@@ -547,7 +551,7 @@ impl App {
|
||||
"[{}] Workflow '{}' completed successfully!",
|
||||
timestamp, workflow.name
|
||||
));
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"[{}] Workflow '{}' completed successfully!",
|
||||
timestamp, workflow.name
|
||||
));
|
||||
@@ -559,7 +563,7 @@ impl App {
|
||||
"[{}] Workflow '{}' failed: {}",
|
||||
timestamp, workflow.name, e
|
||||
));
|
||||
logging::error(&format!(
|
||||
wrkflw_logging::error(&format!(
|
||||
"[{}] Workflow '{}' failed: {}",
|
||||
timestamp, workflow.name, e
|
||||
));
|
||||
@@ -585,7 +589,7 @@ impl App {
|
||||
self.current_execution = Some(next);
|
||||
self.logs
|
||||
.push(format!("Executing workflow: {}", self.workflows[next].name));
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Executing workflow: {}",
|
||||
self.workflows[next].name
|
||||
));
|
||||
@@ -688,7 +692,7 @@ impl App {
|
||||
for log in &self.logs {
|
||||
all_logs.push(log.clone());
|
||||
}
|
||||
for log in logging::get_logs() {
|
||||
for log in wrkflw_logging::get_logs() {
|
||||
all_logs.push(log.clone());
|
||||
}
|
||||
|
||||
@@ -780,7 +784,7 @@ impl App {
|
||||
// Scroll logs down
|
||||
pub fn scroll_logs_down(&mut self) {
|
||||
// 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 {
|
||||
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();
|
||||
self.logs
|
||||
.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;
|
||||
}
|
||||
|
||||
@@ -844,7 +850,7 @@ impl App {
|
||||
"[{}] Triggering workflow: {}",
|
||||
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
|
||||
let workflow_name = workflow.name.clone();
|
||||
@@ -877,19 +883,19 @@ impl App {
|
||||
|
||||
// Send the result back to the main thread
|
||||
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 {
|
||||
let timestamp = Local::now().format("%H:%M:%S").to_string();
|
||||
self.logs
|
||||
.push(format!("[{}] No workflow selected to trigger", timestamp));
|
||||
logging::warning("No workflow selected to trigger");
|
||||
wrkflw_logging::warning("No workflow selected to trigger");
|
||||
}
|
||||
} else {
|
||||
self.logs
|
||||
.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",
|
||||
timestamp
|
||||
));
|
||||
logging::warning("No workflow selected for reset");
|
||||
wrkflw_logging::warning("No workflow selected for reset");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -939,7 +945,7 @@ impl App {
|
||||
"[{}] Reset workflow '{}' from {} state to NotStarted - status is now {:?}",
|
||||
timestamp, workflow.name, old_status, workflow.status
|
||||
));
|
||||
logging::info(&format!(
|
||||
wrkflw_logging::info(&format!(
|
||||
"Reset workflow '{}' from {} state to NotStarted - status is now {:?}",
|
||||
workflow.name, old_status, workflow.status
|
||||
));
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
use crate::app::App;
|
||||
use crate::models::{ExecutionResultMsg, WorkflowExecution, WorkflowStatus};
|
||||
use chrono::Local;
|
||||
use evaluator::evaluate_workflow_file;
|
||||
use executor::{self, JobStatus, RuntimeType, StepStatus};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use wrkflw_evaluator::evaluate_workflow_file;
|
||||
use wrkflw_executor::{self, JobStatus, RuntimeType, StepStatus};
|
||||
|
||||
// Validate a workflow or directory containing workflows
|
||||
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_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);
|
||||
}
|
||||
}
|
||||
@@ -105,18 +105,18 @@ pub async fn execute_workflow_cli(
|
||||
// Check container runtime availability if container runtime is selected
|
||||
let runtime_type = match runtime_type {
|
||||
RuntimeType::Docker => {
|
||||
if !executor::docker::is_available() {
|
||||
if !wrkflw_executor::docker::is_available() {
|
||||
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
|
||||
} else {
|
||||
RuntimeType::Docker
|
||||
}
|
||||
}
|
||||
RuntimeType::Podman => {
|
||||
if !executor::podman::is_available() {
|
||||
if !wrkflw_executor::podman::is_available() {
|
||||
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
|
||||
} else {
|
||||
RuntimeType::Podman
|
||||
@@ -129,20 +129,20 @@ pub async fn execute_workflow_cli(
|
||||
println!("Runtime mode: {:?}", runtime_type);
|
||||
|
||||
// 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={}",
|
||||
path.display(),
|
||||
runtime_type,
|
||||
verbose
|
||||
));
|
||||
|
||||
let config = executor::ExecutionConfig {
|
||||
let config = wrkflw_executor::ExecutionConfig {
|
||||
runtime_type,
|
||||
verbose,
|
||||
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) => {
|
||||
println!("\nWorkflow execution results:");
|
||||
|
||||
@@ -166,7 +166,7 @@ pub async fn execute_workflow_cli(
|
||||
println!("-------------------------");
|
||||
|
||||
// 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() {
|
||||
match step.status {
|
||||
@@ -202,7 +202,7 @@ pub async fn execute_workflow_cli(
|
||||
}
|
||||
|
||||
// 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
|
||||
.output
|
||||
.lines()
|
||||
@@ -242,7 +242,7 @@ pub async fn execute_workflow_cli(
|
||||
}
|
||||
|
||||
// Always log the step details for debug purposes
|
||||
logging::debug(&format!(
|
||||
wrkflw_logging::debug(&format!(
|
||||
"Step: {}, Status: {:?}, Output length: {} lines",
|
||||
step.name,
|
||||
step.status,
|
||||
@@ -250,10 +250,10 @@ pub async fn execute_workflow_cli(
|
||||
));
|
||||
|
||||
// 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()
|
||||
{
|
||||
logging::debug(&format!(
|
||||
wrkflw_logging::debug(&format!(
|
||||
"Step output for '{}': \n{}",
|
||||
step.name, step.output
|
||||
));
|
||||
@@ -265,7 +265,7 @@ pub async fn execute_workflow_cli(
|
||||
println!("\n❌ Workflow completed with failures");
|
||||
// In the case of failure, we'll also inform the user about the debug option
|
||||
// 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");
|
||||
}
|
||||
} else {
|
||||
@@ -276,7 +276,7 @@ pub async fn execute_workflow_cli(
|
||||
}
|
||||
Err(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))
|
||||
}
|
||||
}
|
||||
@@ -286,7 +286,7 @@ pub async fn execute_workflow_cli(
|
||||
pub async fn execute_curl_trigger(
|
||||
workflow_name: &str,
|
||||
branch: Option<&str>,
|
||||
) -> Result<(Vec<executor::JobResult>, ()), String> {
|
||||
) -> Result<(Vec<wrkflw_executor::JobResult>, ()), String> {
|
||||
// Get GitHub token
|
||||
let token = std::env::var("GITHUB_TOKEN").map_err(|_| {
|
||||
"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
|
||||
match std::env::var("GITHUB_TOKEN") {
|
||||
Ok(token) => logging::info(&format!("GITHUB_TOKEN is set: {}", &token[..5])), // Log first 5 characters for security
|
||||
Err(_) => logging::error("GITHUB_TOKEN is not set"),
|
||||
Ok(token) => wrkflw_logging::info(&format!("GITHUB_TOKEN is set: {}", &token[..5])), // Log first 5 characters for security
|
||||
Err(_) => wrkflw_logging::error("GITHUB_TOKEN is not set"),
|
||||
}
|
||||
|
||||
// Get repository information
|
||||
let repo_info =
|
||||
github::get_repo_info().map_err(|e| format!("Failed to get repository info: {}", e))?;
|
||||
let repo_info = wrkflw_github::get_repo_info()
|
||||
.map_err(|e| format!("Failed to get repository info: {}", e))?;
|
||||
|
||||
// Determine branch to use
|
||||
let branch_ref = branch.unwrap_or(&repo_info.default_branch);
|
||||
@@ -315,7 +315,7 @@ pub async fn execute_curl_trigger(
|
||||
workflow_name
|
||||
};
|
||||
|
||||
logging::info(&format!("Using workflow name: {}", workflow_name));
|
||||
wrkflw_logging::info(&format!("Using workflow name: {}", workflow_name));
|
||||
|
||||
// Construct JSON payload
|
||||
let payload = serde_json::json!({
|
||||
@@ -328,7 +328,7 @@ pub async fn execute_curl_trigger(
|
||||
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
|
||||
let client = reqwest::Client::new();
|
||||
@@ -362,12 +362,12 @@ pub async fn execute_curl_trigger(
|
||||
);
|
||||
|
||||
// Create a job result structure
|
||||
let job_result = executor::JobResult {
|
||||
let job_result = wrkflw_executor::JobResult {
|
||||
name: "GitHub Trigger".to_string(),
|
||||
status: executor::JobStatus::Success,
|
||||
steps: vec![executor::StepResult {
|
||||
status: wrkflw_executor::JobStatus::Success,
|
||||
steps: vec![wrkflw_executor::StepResult {
|
||||
name: "Remote Trigger".to_string(),
|
||||
status: executor::StepStatus::Success,
|
||||
status: wrkflw_executor::StepStatus::Success,
|
||||
output: success_msg,
|
||||
}],
|
||||
logs: "Workflow triggered remotely on GitHub".to_string(),
|
||||
@@ -391,13 +391,13 @@ pub fn start_next_workflow_execution(
|
||||
if verbose {
|
||||
app.logs
|
||||
.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 {
|
||||
app.logs.push(
|
||||
"Standard mode: Only step status will be shown (use --verbose for full output)"
|
||||
.to_string(),
|
||||
);
|
||||
logging::info(
|
||||
wrkflw_logging::info(
|
||||
"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 {
|
||||
RuntimeType::Docker => {
|
||||
// Use safe FD redirection to check Docker availability
|
||||
let is_docker_available =
|
||||
match utils::fd::with_stderr_to_null(executor::docker::is_available) {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::debug(
|
||||
"Failed to redirect stderr when checking Docker availability.",
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
let is_docker_available = match wrkflw_utils::fd::with_stderr_to_null(
|
||||
wrkflw_executor::docker::is_available,
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
wrkflw_logging::debug(
|
||||
"Failed to redirect stderr when checking Docker availability.",
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !is_docker_available {
|
||||
app.logs
|
||||
.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
|
||||
} else {
|
||||
RuntimeType::Docker
|
||||
@@ -428,21 +431,24 @@ pub fn start_next_workflow_execution(
|
||||
}
|
||||
RuntimeType::Podman => {
|
||||
// Use safe FD redirection to check Podman availability
|
||||
let is_podman_available =
|
||||
match utils::fd::with_stderr_to_null(executor::podman::is_available) {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::debug(
|
||||
"Failed to redirect stderr when checking Podman availability.",
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
let is_podman_available = match wrkflw_utils::fd::with_stderr_to_null(
|
||||
wrkflw_executor::podman::is_available,
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
wrkflw_logging::debug(
|
||||
"Failed to redirect stderr when checking Podman availability.",
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !is_podman_available {
|
||||
app.logs
|
||||
.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
|
||||
} else {
|
||||
RuntimeType::Podman
|
||||
@@ -487,21 +493,21 @@ pub fn start_next_workflow_execution(
|
||||
Ok(validation_result) => {
|
||||
// Create execution result based on validation
|
||||
let status = if validation_result.is_valid {
|
||||
executor::JobStatus::Success
|
||||
wrkflw_executor::JobStatus::Success
|
||||
} else {
|
||||
executor::JobStatus::Failure
|
||||
wrkflw_executor::JobStatus::Failure
|
||||
};
|
||||
|
||||
// Create a synthetic job result for validation
|
||||
let jobs = vec![executor::JobResult {
|
||||
let jobs = vec![wrkflw_executor::JobResult {
|
||||
name: "Validation".to_string(),
|
||||
status,
|
||||
steps: vec![executor::StepResult {
|
||||
steps: vec![wrkflw_executor::StepResult {
|
||||
name: "Validator".to_string(),
|
||||
status: if validation_result.is_valid {
|
||||
executor::StepStatus::Success
|
||||
wrkflw_executor::StepStatus::Success
|
||||
} else {
|
||||
executor::StepStatus::Failure
|
||||
wrkflw_executor::StepStatus::Failure
|
||||
},
|
||||
output: validation_result.issues.join("\n"),
|
||||
}],
|
||||
@@ -521,15 +527,15 @@ pub fn start_next_workflow_execution(
|
||||
}
|
||||
} else {
|
||||
// Use safe FD redirection for execution
|
||||
let config = executor::ExecutionConfig {
|
||||
let config = wrkflw_executor::ExecutionConfig {
|
||||
runtime_type,
|
||||
verbose,
|
||||
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 {
|
||||
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))?;
|
||||
@@ -546,7 +552,7 @@ pub fn start_next_workflow_execution(
|
||||
|
||||
// Only send if we get a valid 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 {
|
||||
@@ -554,6 +560,6 @@ pub fn start_next_workflow_execution(
|
||||
let timestamp = Local::now().format("%H:%M:%S").to_string();
|
||||
app.logs
|
||||
.push(format!("[{}] All workflows completed execution", timestamp));
|
||||
logging::info("All workflows completed execution");
|
||||
wrkflw_logging::info("All workflows completed execution");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// UI Models for wrkflw
|
||||
use chrono::Local;
|
||||
use executor::{JobStatus, StepStatus};
|
||||
use std::path::PathBuf;
|
||||
use wrkflw_executor::{JobStatus, StepStatus};
|
||||
|
||||
/// 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
|
||||
pub struct Workflow {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// UI utilities
|
||||
use crate::models::{Workflow, WorkflowStatus};
|
||||
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
|
||||
pub fn load_workflows(dir_path: &Path) -> Vec<Workflow> {
|
||||
|
||||
@@ -145,15 +145,17 @@ pub fn render_execution_tab(
|
||||
.iter()
|
||||
.map(|job| {
|
||||
let status_symbol = match job.status {
|
||||
executor::JobStatus::Success => "✅",
|
||||
executor::JobStatus::Failure => "❌",
|
||||
executor::JobStatus::Skipped => "⏭",
|
||||
wrkflw_executor::JobStatus::Success => "✅",
|
||||
wrkflw_executor::JobStatus::Failure => "❌",
|
||||
wrkflw_executor::JobStatus::Skipped => "⏭",
|
||||
};
|
||||
|
||||
let status_style = match job.status {
|
||||
executor::JobStatus::Success => Style::default().fg(Color::Green),
|
||||
executor::JobStatus::Failure => Style::default().fg(Color::Red),
|
||||
executor::JobStatus::Skipped => Style::default().fg(Color::Gray),
|
||||
wrkflw_executor::JobStatus::Success => {
|
||||
Style::default().fg(Color::Green)
|
||||
}
|
||||
wrkflw_executor::JobStatus::Failure => Style::default().fg(Color::Red),
|
||||
wrkflw_executor::JobStatus::Skipped => Style::default().fg(Color::Gray),
|
||||
};
|
||||
|
||||
// Count completed and total steps
|
||||
@@ -162,8 +164,8 @@ pub fn render_execution_tab(
|
||||
.steps
|
||||
.iter()
|
||||
.filter(|s| {
|
||||
s.status == executor::StepStatus::Success
|
||||
|| s.status == executor::StepStatus::Failure
|
||||
s.status == wrkflw_executor::StepStatus::Success
|
||||
|| s.status == wrkflw_executor::StepStatus::Failure
|
||||
})
|
||||
.count();
|
||||
|
||||
|
||||
@@ -46,15 +46,15 @@ pub fn render_job_detail_view(
|
||||
|
||||
// Job title section
|
||||
let status_text = match job.status {
|
||||
executor::JobStatus::Success => "Success",
|
||||
executor::JobStatus::Failure => "Failed",
|
||||
executor::JobStatus::Skipped => "Skipped",
|
||||
wrkflw_executor::JobStatus::Success => "Success",
|
||||
wrkflw_executor::JobStatus::Failure => "Failed",
|
||||
wrkflw_executor::JobStatus::Skipped => "Skipped",
|
||||
};
|
||||
|
||||
let status_style = match job.status {
|
||||
executor::JobStatus::Success => Style::default().fg(Color::Green),
|
||||
executor::JobStatus::Failure => Style::default().fg(Color::Red),
|
||||
executor::JobStatus::Skipped => Style::default().fg(Color::Yellow),
|
||||
wrkflw_executor::JobStatus::Success => Style::default().fg(Color::Green),
|
||||
wrkflw_executor::JobStatus::Failure => Style::default().fg(Color::Red),
|
||||
wrkflw_executor::JobStatus::Skipped => Style::default().fg(Color::Yellow),
|
||||
};
|
||||
|
||||
let job_title = Paragraph::new(vec![
|
||||
@@ -101,15 +101,19 @@ pub fn render_job_detail_view(
|
||||
|
||||
let rows = job.steps.iter().map(|step| {
|
||||
let status_symbol = match step.status {
|
||||
executor::StepStatus::Success => "✅",
|
||||
executor::StepStatus::Failure => "❌",
|
||||
executor::StepStatus::Skipped => "⏭",
|
||||
wrkflw_executor::StepStatus::Success => "✅",
|
||||
wrkflw_executor::StepStatus::Failure => "❌",
|
||||
wrkflw_executor::StepStatus::Skipped => "⏭",
|
||||
};
|
||||
|
||||
let status_style = match step.status {
|
||||
executor::StepStatus::Success => Style::default().fg(Color::Green),
|
||||
executor::StepStatus::Failure => Style::default().fg(Color::Red),
|
||||
executor::StepStatus::Skipped => Style::default().fg(Color::Gray),
|
||||
wrkflw_executor::StepStatus::Success => {
|
||||
Style::default().fg(Color::Green)
|
||||
}
|
||||
wrkflw_executor::StepStatus::Failure => Style::default().fg(Color::Red),
|
||||
wrkflw_executor::StepStatus::Skipped => {
|
||||
Style::default().fg(Color::Gray)
|
||||
}
|
||||
};
|
||||
|
||||
Row::new(vec![
|
||||
@@ -147,15 +151,21 @@ pub fn render_job_detail_view(
|
||||
|
||||
// Show step output with proper styling
|
||||
let status_text = match step.status {
|
||||
executor::StepStatus::Success => "Success",
|
||||
executor::StepStatus::Failure => "Failed",
|
||||
executor::StepStatus::Skipped => "Skipped",
|
||||
wrkflw_executor::StepStatus::Success => "Success",
|
||||
wrkflw_executor::StepStatus::Failure => "Failed",
|
||||
wrkflw_executor::StepStatus::Skipped => "Skipped",
|
||||
};
|
||||
|
||||
let status_style = match step.status {
|
||||
executor::StepStatus::Success => Style::default().fg(Color::Green),
|
||||
executor::StepStatus::Failure => Style::default().fg(Color::Red),
|
||||
executor::StepStatus::Skipped => Style::default().fg(Color::Yellow),
|
||||
wrkflw_executor::StepStatus::Success => {
|
||||
Style::default().fg(Color::Green)
|
||||
}
|
||||
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();
|
||||
|
||||
@@ -151,7 +151,7 @@ pub fn render_logs_tab(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &App, a
|
||||
}
|
||||
|
||||
// Process system logs
|
||||
for log in logging::get_logs() {
|
||||
for log in wrkflw_logging::get_logs() {
|
||||
all_logs.push(log.clone());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Status bar rendering
|
||||
use crate::app::App;
|
||||
use executor::RuntimeType;
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
layout::{Alignment, Rect},
|
||||
@@ -10,6 +9,7 @@ use ratatui::{
|
||||
Frame,
|
||||
};
|
||||
use std::io;
|
||||
use wrkflw_executor::RuntimeType;
|
||||
|
||||
// Render the status bar
|
||||
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 {
|
||||
RuntimeType::Docker => {
|
||||
// Check Docker silently using safe FD redirection
|
||||
let is_docker_available =
|
||||
match utils::fd::with_stderr_to_null(executor::docker::is_available) {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::debug(
|
||||
"Failed to redirect stderr when checking Docker availability.",
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
let is_docker_available = match wrkflw_utils::fd::with_stderr_to_null(
|
||||
wrkflw_executor::docker::is_available,
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
wrkflw_logging::debug(
|
||||
"Failed to redirect stderr when checking Docker availability.",
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
status_items.push(Span::raw(" "));
|
||||
status_items.push(Span::styled(
|
||||
@@ -79,16 +80,17 @@ pub fn render_status_bar(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &App,
|
||||
}
|
||||
RuntimeType::Podman => {
|
||||
// Check Podman silently using safe FD redirection
|
||||
let is_podman_available =
|
||||
match utils::fd::with_stderr_to_null(executor::podman::is_available) {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
logging::debug(
|
||||
"Failed to redirect stderr when checking Podman availability.",
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
let is_podman_available = match wrkflw_utils::fd::with_stderr_to_null(
|
||||
wrkflw_executor::podman::is_available,
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
wrkflw_logging::debug(
|
||||
"Failed to redirect stderr when checking Podman availability.",
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
status_items.push(Span::raw(" "));
|
||||
status_items.push(Span::styled(
|
||||
@@ -159,7 +161,7 @@ pub fn render_status_bar(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &App,
|
||||
}
|
||||
2 => {
|
||||
// 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 {
|
||||
// Convert to a static string for consistent return type
|
||||
let scroll_text = format!(
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
[package]
|
||||
name = "utils"
|
||||
name = "wrkflw-utils"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "utility functions for wrkflw"
|
||||
description = "Utility functions for wrkflw workflow execution engine"
|
||||
license.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
wrkflw-models = { path = "../models", version = "0.6.0" }
|
||||
|
||||
# External dependencies
|
||||
serde.workspace = true
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
[package]
|
||||
name = "validators"
|
||||
name = "wrkflw-validators"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "validation functionality for wrkflw"
|
||||
description = "Workflow validation functionality for wrkflw execution engine"
|
||||
license.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
matrix = { path = "../matrix" }
|
||||
wrkflw-models = { path = "../models", version = "0.6.0" }
|
||||
wrkflw-matrix = { path = "../matrix", version = "0.6.0" }
|
||||
|
||||
# External dependencies
|
||||
serde.workspace = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use models::ValidationResult;
|
||||
use wrkflw_models::ValidationResult;
|
||||
|
||||
pub fn validate_action_reference(
|
||||
action_ref: &str,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use models::gitlab::{Job, Pipeline};
|
||||
use models::ValidationResult;
|
||||
use std::collections::HashMap;
|
||||
use wrkflw_models::gitlab::{Job, Pipeline};
|
||||
use wrkflw_models::ValidationResult;
|
||||
|
||||
/// Validate a GitLab CI/CD pipeline
|
||||
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
|
||||
if let Some(retry) = &job.retry {
|
||||
match retry {
|
||||
models::gitlab::Retry::MaxAttempts(attempts) => {
|
||||
wrkflw_models::gitlab::Retry::MaxAttempts(attempts) => {
|
||||
if *attempts > 10 {
|
||||
result.add_issue(format!(
|
||||
"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 {
|
||||
result.add_issue(format!(
|
||||
"Job '{}' has excessive retry count: {}. Consider reducing to avoid resource waste",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{validate_matrix, validate_steps};
|
||||
use models::ValidationResult;
|
||||
use serde_yaml::Value;
|
||||
use wrkflw_models::ValidationResult;
|
||||
|
||||
pub fn validate_jobs(jobs: &Value, result: &mut ValidationResult) {
|
||||
if let Value::Mapping(jobs_map) = jobs {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use models::ValidationResult;
|
||||
use serde_yaml::Value;
|
||||
use wrkflw_models::ValidationResult;
|
||||
|
||||
pub fn validate_matrix(matrix: &Value, result: &mut ValidationResult) {
|
||||
// Check if matrix is a mapping
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::validate_action_reference;
|
||||
use models::ValidationResult;
|
||||
use serde_yaml::Value;
|
||||
use std::collections::HashSet;
|
||||
use wrkflw_models::ValidationResult;
|
||||
|
||||
pub fn validate_steps(steps: &[Value], job_name: &str, result: &mut ValidationResult) {
|
||||
let mut step_ids: HashSet<String> = HashSet::new();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use models::ValidationResult;
|
||||
use serde_yaml::Value;
|
||||
use wrkflw_models::ValidationResult;
|
||||
|
||||
pub fn validate_triggers(on: &Value, result: &mut ValidationResult) {
|
||||
let valid_events = vec![
|
||||
|
||||
@@ -12,18 +12,18 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Workspace crates
|
||||
models = { path = "../models" }
|
||||
executor = { path = "../executor" }
|
||||
github = { path = "../github" }
|
||||
gitlab = { path = "../gitlab" }
|
||||
logging = { path = "../logging" }
|
||||
matrix = { path = "../matrix" }
|
||||
parser = { path = "../parser" }
|
||||
runtime = { path = "../runtime" }
|
||||
ui = { path = "../ui" }
|
||||
utils = { path = "../utils" }
|
||||
validators = { path = "../validators" }
|
||||
evaluator = { path = "../evaluator" }
|
||||
wrkflw-models = { path = "../models", version = "0.6.0" }
|
||||
wrkflw-executor = { path = "../executor", version = "0.6.0" }
|
||||
wrkflw-github = { path = "../github", version = "0.6.0" }
|
||||
wrkflw-gitlab = { path = "../gitlab", version = "0.6.0" }
|
||||
wrkflw-logging = { path = "../logging", version = "0.6.0" }
|
||||
wrkflw-matrix = { path = "../matrix", version = "0.6.0" }
|
||||
wrkflw-parser = { path = "../parser", version = "0.6.0" }
|
||||
wrkflw-runtime = { path = "../runtime", version = "0.6.0" }
|
||||
wrkflw-ui = { path = "../ui", version = "0.6.0" }
|
||||
wrkflw-utils = { path = "../utils", version = "0.6.0" }
|
||||
wrkflw-validators = { path = "../validators", version = "0.6.0" }
|
||||
wrkflw-evaluator = { path = "../evaluator", version = "0.6.0" }
|
||||
|
||||
# External dependencies
|
||||
clap.workspace = true
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
pub use evaluator;
|
||||
pub use executor;
|
||||
pub use github;
|
||||
pub use gitlab;
|
||||
pub use logging;
|
||||
pub use matrix;
|
||||
pub use models;
|
||||
pub use parser;
|
||||
pub use runtime;
|
||||
pub use ui;
|
||||
pub use utils;
|
||||
pub use validators;
|
||||
pub use wrkflw_evaluator as evaluator;
|
||||
pub use wrkflw_executor as executor;
|
||||
pub use wrkflw_github as github;
|
||||
pub use wrkflw_gitlab as gitlab;
|
||||
pub use wrkflw_logging as logging;
|
||||
pub use wrkflw_matrix as matrix;
|
||||
pub use wrkflw_models as models;
|
||||
pub use wrkflw_parser as parser;
|
||||
pub use wrkflw_runtime as runtime;
|
||||
pub use wrkflw_ui as ui;
|
||||
pub use wrkflw_utils as utils;
|
||||
pub use wrkflw_validators as validators;
|
||||
|
||||
@@ -14,12 +14,12 @@ enum RuntimeChoice {
|
||||
Emulation,
|
||||
}
|
||||
|
||||
impl From<RuntimeChoice> for executor::RuntimeType {
|
||||
impl From<RuntimeChoice> for wrkflw_executor::RuntimeType {
|
||||
fn from(choice: RuntimeChoice) -> Self {
|
||||
match choice {
|
||||
RuntimeChoice::Docker => executor::RuntimeType::Docker,
|
||||
RuntimeChoice::Podman => executor::RuntimeType::Podman,
|
||||
RuntimeChoice::Emulation => executor::RuntimeType::Emulation,
|
||||
RuntimeChoice::Docker => wrkflw_executor::RuntimeType::Docker,
|
||||
RuntimeChoice::Podman => wrkflw_executor::RuntimeType::Podman,
|
||||
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?
|
||||
// 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.
|
||||
async fn cleanup_on_exit() {
|
||||
// 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() {
|
||||
Ok(docker) => {
|
||||
// Assuming cleanup_resources exists in executor crate
|
||||
executor::cleanup_resources(&docker).await;
|
||||
wrkflw_executor::cleanup_resources(&docker).await;
|
||||
}
|
||||
Err(_) => {
|
||||
// Docker not available
|
||||
logging::info("Docker not available, skipping Docker cleanup");
|
||||
wrkflw_logging::info("Docker not available, skipping Docker cleanup");
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => logging::debug("Docker cleanup completed successfully"),
|
||||
Err(_) => {
|
||||
logging::warning("Docker cleanup timed out after 3 seconds, continuing with shutdown")
|
||||
}
|
||||
Ok(_) => wrkflw_logging::debug("Docker cleanup completed successfully"),
|
||||
Err(_) => wrkflw_logging::warning(
|
||||
"Docker cleanup timed out after 3 seconds, continuing with shutdown",
|
||||
),
|
||||
}
|
||||
|
||||
// Always clean up emulation resources
|
||||
match tokio::time::timeout(
|
||||
std::time::Duration::from_secs(2),
|
||||
// Assuming cleanup_resources exists in runtime::emulation module
|
||||
runtime::emulation::cleanup_resources(),
|
||||
// Assuming cleanup_resources exists in wrkflw_runtime::emulation module
|
||||
wrkflw_runtime::emulation::cleanup_resources(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => logging::debug("Emulation cleanup completed successfully"),
|
||||
Err(_) => logging::warning("Emulation cleanup timed out, continuing with shutdown"),
|
||||
Ok(_) => wrkflw_logging::debug("Emulation cleanup completed successfully"),
|
||||
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() {
|
||||
@@ -207,7 +207,7 @@ async fn handle_signals() {
|
||||
"Cleanup taking too long (over {} seconds), forcing exit...",
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -272,13 +272,13 @@ async fn main() {
|
||||
|
||||
// Set log level based on command line flags
|
||||
if debug {
|
||||
logging::set_log_level(logging::LogLevel::Debug);
|
||||
logging::debug("Debug mode enabled - showing detailed logs");
|
||||
wrkflw_logging::set_log_level(wrkflw_logging::LogLevel::Debug);
|
||||
wrkflw_logging::debug("Debug mode enabled - showing detailed logs");
|
||||
} else if verbose {
|
||||
logging::set_log_level(logging::LogLevel::Info);
|
||||
logging::info("Verbose mode enabled");
|
||||
wrkflw_logging::set_log_level(wrkflw_logging::LogLevel::Info);
|
||||
wrkflw_logging::info("Verbose mode enabled");
|
||||
} 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
|
||||
@@ -360,7 +360,7 @@ async fn main() {
|
||||
gitlab,
|
||||
}) => {
|
||||
// Create execution configuration
|
||||
let config = executor::ExecutionConfig {
|
||||
let config = wrkflw_executor::ExecutionConfig {
|
||||
runtime_type: runtime.clone().into(),
|
||||
verbose,
|
||||
preserve_containers_on_failure: *preserve_containers_on_failure,
|
||||
@@ -374,10 +374,10 @@ async fn main() {
|
||||
"GitHub workflow"
|
||||
};
|
||||
|
||||
logging::info(&format!("Running {} at: {}", workflow_type, path.display()));
|
||||
wrkflw_logging::info(&format!("Running {} at: {}", workflow_type, path.display()));
|
||||
|
||||
// Execute the workflow
|
||||
let result = executor::execute_workflow(path, config)
|
||||
let result = wrkflw_executor::execute_workflow(path, config)
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("Error executing workflow: {}", e);
|
||||
@@ -419,15 +419,15 @@ async fn main() {
|
||||
println!(
|
||||
" {} {} ({})",
|
||||
match job.status {
|
||||
executor::JobStatus::Success => "✅",
|
||||
executor::JobStatus::Failure => "❌",
|
||||
executor::JobStatus::Skipped => "⏭️",
|
||||
wrkflw_executor::JobStatus::Success => "✅",
|
||||
wrkflw_executor::JobStatus::Failure => "❌",
|
||||
wrkflw_executor::JobStatus::Skipped => "⏭️",
|
||||
},
|
||||
job.name,
|
||||
match job.status {
|
||||
executor::JobStatus::Success => "success",
|
||||
executor::JobStatus::Failure => "failure",
|
||||
executor::JobStatus::Skipped => "skipped",
|
||||
wrkflw_executor::JobStatus::Success => "success",
|
||||
wrkflw_executor::JobStatus::Failure => "failure",
|
||||
wrkflw_executor::JobStatus::Skipped => "skipped",
|
||||
}
|
||||
);
|
||||
|
||||
@@ -435,15 +435,15 @@ async fn main() {
|
||||
println!(" Steps:");
|
||||
for step in job.steps {
|
||||
let step_status = match step.status {
|
||||
executor::StepStatus::Success => "✅",
|
||||
executor::StepStatus::Failure => "❌",
|
||||
executor::StepStatus::Skipped => "⏭️",
|
||||
wrkflw_executor::StepStatus::Success => "✅",
|
||||
wrkflw_executor::StepStatus::Failure => "❌",
|
||||
wrkflw_executor::StepStatus::Skipped => "⏭️",
|
||||
};
|
||||
|
||||
println!(" {} {}", step_status, step.name);
|
||||
|
||||
// If step failed and we're not in verbose mode, show condensed error info
|
||||
if step.status == executor::StepStatus::Failure && !verbose {
|
||||
if step.status == wrkflw_executor::StepStatus::Failure && !verbose {
|
||||
// Extract error information from step output
|
||||
let error_lines = step
|
||||
.output
|
||||
@@ -482,7 +482,7 @@ async fn main() {
|
||||
.map(|v| v.iter().cloned().collect::<HashMap<String, String>>());
|
||||
|
||||
// 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);
|
||||
std::process::exit(1);
|
||||
}
|
||||
@@ -497,7 +497,7 @@ async fn main() {
|
||||
let runtime_type = runtime.clone().into();
|
||||
|
||||
// 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(),
|
||||
runtime_type,
|
||||
verbose,
|
||||
@@ -520,7 +520,9 @@ async fn main() {
|
||||
.map(|i| i.iter().cloned().collect::<HashMap<String, String>>());
|
||||
|
||||
// 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);
|
||||
std::process::exit(1);
|
||||
}
|
||||
@@ -530,10 +532,10 @@ async fn main() {
|
||||
}
|
||||
None => {
|
||||
// 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
|
||||
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);
|
||||
std::process::exit(1);
|
||||
}
|
||||
@@ -547,13 +549,13 @@ fn validate_github_workflow(path: &Path, verbose: bool) -> bool {
|
||||
print!("Validating GitHub workflow file: {}... ", path.display());
|
||||
|
||||
// Use the ui crate's validate_workflow function
|
||||
match ui::validate_workflow(path, verbose) {
|
||||
match wrkflw_ui::validate_workflow(path, verbose) {
|
||||
Ok(_) => {
|
||||
// The detailed validation output is already printed by the function
|
||||
// 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
|
||||
match evaluator::evaluate_workflow_file(path, verbose) {
|
||||
match wrkflw_evaluator::evaluate_workflow_file(path, verbose) {
|
||||
Ok(result) => !result.is_valid,
|
||||
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());
|
||||
|
||||
// Parse and validate the pipeline file
|
||||
match parser::gitlab::parse_pipeline(path) {
|
||||
match wrkflw_parser::gitlab::parse_pipeline(path) {
|
||||
Ok(pipeline) => {
|
||||
println!("✅ Valid syntax");
|
||||
|
||||
// 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 {
|
||||
println!("⚠️ Validation issues:");
|
||||
|
||||
71
publish_crates.sh
Executable file
71
publish_crates.sh
Executable 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
|
||||
@@ -1,6 +1,6 @@
|
||||
# Testing Strategy
|
||||
|
||||
This directory contains integration tests for the `wrkflw` project. We follow the Rust testing best practices by organizing tests as follows:
|
||||
This directory contains all tests and test-related files for the `wrkflw` project. We follow the Rust testing best practices by organizing tests as follows:
|
||||
|
||||
## Test Organization
|
||||
|
||||
@@ -11,6 +11,17 @@ This directory contains integration tests for the `wrkflw` project. We follow th
|
||||
- **End-to-End Tests**: Also located in this `tests/` directory
|
||||
- `cleanup_test.rs` - Tests for cleanup functionality with Docker resources
|
||||
|
||||
## Test Directory Structure
|
||||
|
||||
- **`fixtures/`**: Test data and configuration files
|
||||
- `gitlab-ci/` - GitLab CI configuration files for testing
|
||||
- **`workflows/`**: GitHub Actions workflow files for testing
|
||||
- Various YAML files for testing workflow validation and execution
|
||||
- **`scripts/`**: Test automation scripts
|
||||
- `test-podman-basic.sh` - Basic Podman integration test script
|
||||
- `test-preserve-containers.sh` - Container preservation testing script
|
||||
- **`TESTING_PODMAN.md`**: Comprehensive Podman testing documentation
|
||||
|
||||
## Running Tests
|
||||
|
||||
To run all tests:
|
||||
|
||||
@@ -70,21 +70,21 @@ Expected: Should show `--runtime` option with default value `docker`.
|
||||
#### 1.2 Test Podman Runtime Selection
|
||||
```bash
|
||||
# Should accept podman as runtime
|
||||
./target/release/wrkflw run --runtime podman test-workflows/example.yml
|
||||
./target/release/wrkflw run --runtime podman tests/workflows/example.yml
|
||||
```
|
||||
Expected: Should run without CLI argument errors.
|
||||
|
||||
#### 1.3 Test Emulation Runtime Selection
|
||||
```bash
|
||||
# Should accept emulation as runtime
|
||||
./target/release/wrkflw run --runtime emulation test-workflows/example.yml
|
||||
./target/release/wrkflw run --runtime emulation tests/workflows/example.yml
|
||||
```
|
||||
Expected: Should run without CLI argument errors.
|
||||
|
||||
#### 1.4 Test Invalid Runtime Selection
|
||||
```bash
|
||||
# Should reject invalid runtime
|
||||
./target/release/wrkflw run --runtime invalid test-workflows/example.yml
|
||||
./target/release/wrkflw run --runtime invalid tests/workflows/example.yml
|
||||
```
|
||||
Expected: Should show error about invalid runtime choice.
|
||||
|
||||
@@ -172,7 +172,7 @@ Expected: All three runtimes should produce similar results (emulation may have
|
||||
|
||||
#### 4.1 Test TUI Runtime Selection
|
||||
```bash
|
||||
./target/release/wrkflw tui test-workflows/
|
||||
./target/release/wrkflw tui tests/workflows/
|
||||
```
|
||||
|
||||
**Test Steps:**
|
||||
@@ -59,7 +59,7 @@ fi
|
||||
|
||||
# Test 2: Check invalid runtime rejection
|
||||
print_status "Test 2: Testing invalid runtime rejection..."
|
||||
if ./target/release/wrkflw run --runtime invalid test-workflows/example.yml 2>&1 | grep -q "invalid value"; then
|
||||
if ./target/release/wrkflw run --runtime invalid tests/workflows/example.yml 2>&1 | grep -q "invalid value"; then
|
||||
print_success "Invalid runtime properly rejected"
|
||||
else
|
||||
print_error "Invalid runtime not properly rejected"
|
||||
Reference in New Issue
Block a user