mirror of
https://github.com/bahdotsh/wrkflw.git
synced 2026-02-24 03:49:45 +01:00
Refactor: Migrate modules to workspace crates
- Extracted functionality from the `src/` directory into individual crates within the `crates/` directory. This improves modularity, organization, and separation of concerns. - Migrated modules include: models, evaluator, ui, gitlab, utils, logging, github, matrix, executor, runtime, parser, and validators. - Removed the original source files and directories from `src/` after successful migration. - This change sets the stage for better code management and potentially independent development/versioning of workspace members.
This commit is contained in:
90
.github/test_organization.md
vendored
90
.github/test_organization.md
vendored
@@ -1,90 +0,0 @@
|
||||
# Test Organization for wrkflw
|
||||
|
||||
Following Rust best practices, we have reorganized the tests in this project to improve maintainability and clarity.
|
||||
|
||||
## Test Structure
|
||||
|
||||
Tests are now organized as follows:
|
||||
|
||||
### 1. Unit Tests
|
||||
|
||||
Unit tests remain in the source files using the `#[cfg(test)]` attribute. These tests are designed to test individual functions and small units of code in isolation.
|
||||
|
||||
Example:
|
||||
```rust
|
||||
// In src/matrix.rs
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_function() {
|
||||
// Test code here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Integration Tests
|
||||
|
||||
Integration tests have been moved to the `tests/` directory. These tests import and test the public API of the crate, ensuring that different components work together correctly.
|
||||
|
||||
- `tests/matrix_test.rs` - Tests for matrix expansion functionality
|
||||
- `tests/reusable_workflow_test.rs` - Tests for reusable workflow validation
|
||||
|
||||
### 3. End-to-End Tests
|
||||
|
||||
End-to-end tests are also located in the `tests/` directory. These tests simulate real-world usage scenarios and often involve external dependencies like Docker.
|
||||
|
||||
- `tests/cleanup_test.rs` - Tests for cleanup functionality with Docker containers, networks, etc.
|
||||
|
||||
## Running Tests
|
||||
|
||||
You can run all tests using:
|
||||
```bash
|
||||
cargo test
|
||||
```
|
||||
|
||||
To run only unit tests:
|
||||
```bash
|
||||
cargo test --lib
|
||||
```
|
||||
|
||||
To run only integration tests:
|
||||
```bash
|
||||
cargo test --test matrix_test --test reusable_workflow_test
|
||||
```
|
||||
|
||||
To run only end-to-end tests:
|
||||
```bash
|
||||
cargo test --test cleanup_test
|
||||
```
|
||||
|
||||
To run a specific test:
|
||||
```bash
|
||||
cargo test test_name
|
||||
```
|
||||
|
||||
## CI Configuration
|
||||
|
||||
Our CI workflow has been updated to run all types of tests separately, allowing for better isolation and clearer failure reporting:
|
||||
|
||||
```yaml
|
||||
- name: Run unit tests
|
||||
run: cargo test --lib --verbose
|
||||
|
||||
- name: Run integration tests
|
||||
run: cargo test --test matrix_test --test reusable_workflow_test --verbose
|
||||
|
||||
- name: Run e2e tests (if Docker available)
|
||||
run: cargo test --test cleanup_test --verbose -- --skip docker --skip processes
|
||||
```
|
||||
|
||||
## Writing New Tests
|
||||
|
||||
When adding new tests:
|
||||
|
||||
1. For unit tests, add them to the relevant source file using `#[cfg(test)]`
|
||||
2. For integration tests, add them to the `tests/` directory with a descriptive name like `feature_name_test.rs`
|
||||
3. For end-to-end tests, also add them to the `tests/` directory with a descriptive name
|
||||
|
||||
Follow the existing patterns to ensure consistency.
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -78,10 +78,6 @@ jobs:
|
||||
target: aarch64-apple-darwin
|
||||
artifact_name: wrkflw
|
||||
asset_name: wrkflw-${{ github.event.inputs.version || github.ref_name }}-macos-arm64
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
artifact_name: wrkflw.exe
|
||||
asset_name: wrkflw-${{ github.event.inputs.version || github.ref_name }}-windows-x86_64
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
|
||||
43
.github/workflows/rust.yml
vendored
43
.github/workflows/rust.yml
vendored
@@ -1,43 +0,0 @@
|
||||
name: Rust
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
|
||||
test-unit:
|
||||
needs: [build]
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run unit tests
|
||||
run: cargo test --lib --verbose
|
||||
|
||||
test-integration:
|
||||
needs: [build]
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run integration tests
|
||||
run: cargo test --test matrix_test --test reusable_workflow_test --verbose
|
||||
|
||||
test-e2e:
|
||||
needs: [build]
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run e2e tests (if Docker available)
|
||||
run: cargo test --test cleanup_test --verbose -- --skip docker --skip processes
|
||||
231
Cargo.lock
generated
231
Cargo.lock
generated
@@ -486,6 +486,46 @@ 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"
|
||||
@@ -674,6 +714,35 @@ 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"
|
||||
@@ -729,15 +798,6 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
@@ -1112,12 +1172,6 @@ dependencies = [
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.3"
|
||||
@@ -1146,6 +1200,28 @@ 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"
|
||||
@@ -1190,6 +1266,16 @@ 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"
|
||||
@@ -1410,6 +1496,18 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parser"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"jsonschema",
|
||||
"matrix",
|
||||
"models",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
@@ -1616,25 +1714,26 @@ dependencies = [
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "runtime"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"logging",
|
||||
"models",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.3"
|
||||
@@ -1644,7 +1743,7 @@ dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.3",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -1954,7 +2053,7 @@ dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.2",
|
||||
"once_cell",
|
||||
"rustix 1.0.3",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -2102,6 +2201,28 @@ 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"
|
||||
@@ -2161,6 +2282,16 @@ 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"
|
||||
@@ -2170,6 +2301,16 @@ 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"
|
||||
@@ -2287,18 +2428,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"once_cell",
|
||||
"rustix 0.38.44",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@@ -2519,38 +2648,46 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
name = "wrkflw"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bollard",
|
||||
"chrono",
|
||||
"clap",
|
||||
"colored",
|
||||
"crossterm 0.26.1",
|
||||
"dirs",
|
||||
"evaluator",
|
||||
"executor",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"github",
|
||||
"gitlab",
|
||||
"indexmap 2.8.0",
|
||||
"itertools",
|
||||
"jsonschema",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"logging",
|
||||
"matrix",
|
||||
"models",
|
||||
"nix",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"parser",
|
||||
"ratatui",
|
||||
"rayon",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"runtime",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"tar",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"ui",
|
||||
"urlencoding",
|
||||
"utils",
|
||||
"uuid",
|
||||
"which",
|
||||
"validators",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2560,7 +2697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rustix 1.0.3",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -1,5 +1,10 @@
|
||||
[package]
|
||||
name = "wrkflw"
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/*"
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.4.0"
|
||||
edition = "2021"
|
||||
description = "A GitHub Actions workflow validator and executor"
|
||||
@@ -10,7 +15,7 @@ keywords = ["workflows", "github", "local"]
|
||||
categories = ["command-line-utilities"]
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
[workspace.dependencies]
|
||||
clap = { version = "4.3", features = ["derive"] }
|
||||
colored = "2.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
97
crates/README.md
Normal file
97
crates/README.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Wrkflw Crates
|
||||
|
||||
This directory contains the Rust crates that make up the Wrkflw project. The project has been restructured to use a workspace-based approach with individual crates for better modularity and maintainability.
|
||||
|
||||
## Crate Structure
|
||||
|
||||
- **wrkflw**: Main binary crate and entry point for the application
|
||||
- **models**: Data models and structures used throughout the application
|
||||
- **evaluator**: Workflow evaluation functionality
|
||||
- **executor**: Workflow execution engine
|
||||
- **github**: GitHub API integration
|
||||
- **gitlab**: GitLab API integration
|
||||
- **logging**: Logging functionality
|
||||
- **matrix**: Matrix-based parallelization support
|
||||
- **parser**: Workflow parsing functionality
|
||||
- **runtime**: Runtime execution environment
|
||||
- **ui**: User interface components
|
||||
- **utils**: Utility functions
|
||||
- **validators**: Validation functionality
|
||||
|
||||
## Dependencies
|
||||
|
||||
Each crate has its own `Cargo.toml` file that defines its dependencies. The root `Cargo.toml` file defines the workspace and shared dependencies.
|
||||
|
||||
## Build Instructions
|
||||
|
||||
To build the entire project:
|
||||
|
||||
```bash
|
||||
cargo build
|
||||
```
|
||||
|
||||
To build a specific crate:
|
||||
|
||||
```bash
|
||||
cargo build -p <crate-name>
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
To run tests for the entire project:
|
||||
|
||||
```bash
|
||||
cargo test
|
||||
```
|
||||
|
||||
To run tests for a specific crate:
|
||||
|
||||
```bash
|
||||
cargo test -p <crate-name>
|
||||
```
|
||||
|
||||
## Rust Best Practices
|
||||
|
||||
When contributing to wrkflw, please follow these Rust best practices:
|
||||
|
||||
### Code Organization
|
||||
|
||||
- Place modules in their respective crates to maintain separation of concerns
|
||||
- Use `pub` selectively to expose only the necessary APIs
|
||||
- Follow the Rust module system conventions (use `mod` and `pub mod` appropriately)
|
||||
|
||||
### Errors and Error Handling
|
||||
|
||||
- Prefer using the `thiserror` crate for defining custom error types
|
||||
- Use the `?` operator for error propagation instead of match statements when appropriate
|
||||
- Implement custom error types that provide context for the error
|
||||
- Avoid using `.unwrap()` and `.expect()` in production code
|
||||
|
||||
### Performance
|
||||
|
||||
- Profile code before optimizing using tools like `cargo flamegraph`
|
||||
- Use `Arc` and `Mutex` judiciously for shared mutable state
|
||||
- Leverage Rust's zero-cost abstractions (iterators, closures)
|
||||
- Consider adding benchmark tests using the `criterion` crate for performance-critical code
|
||||
|
||||
### Security
|
||||
|
||||
- Validate all input, especially from external sources
|
||||
- Avoid using `unsafe` code unless absolutely necessary
|
||||
- Handle secrets securely using environment variables
|
||||
- Check for integer overflows with `checked_` operations
|
||||
|
||||
### Testing
|
||||
|
||||
- Write unit tests for all public functions
|
||||
- Use integration tests to verify crate-to-crate interactions
|
||||
- Consider property-based testing for complex logic
|
||||
- Structure tests with clear preparation, execution, and verification phases
|
||||
|
||||
### Tooling
|
||||
|
||||
- Run `cargo clippy` before committing changes to catch common mistakes
|
||||
- Use `cargo fmt` to maintain consistent code formatting
|
||||
- Enable compiler warnings with `#![warn(clippy::all)]`
|
||||
|
||||
For more detailed guidance, refer to the project's best practices documentation.
|
||||
15
crates/evaluator/Cargo.toml
Normal file
15
crates/evaluator/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "evaluator"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Workflow evaluation for wrkflw"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
validators = { path = "../validators" }
|
||||
|
||||
# External dependencies
|
||||
colored.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
@@ -3,8 +3,8 @@ use serde_yaml::{self, Value};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::models::ValidationResult;
|
||||
use crate::validators::{validate_jobs, validate_triggers};
|
||||
use models::ValidationResult;
|
||||
use 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))?;
|
||||
35
crates/executor/Cargo.toml
Normal file
35
crates/executor/Cargo.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "executor"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Workflow executor for wrkflw"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
parser = { path = "../parser" }
|
||||
runtime = { path = "../runtime" }
|
||||
logging = { path = "../logging" }
|
||||
matrix = { path = "../matrix" }
|
||||
utils = { path = "../utils" }
|
||||
|
||||
# External dependencies
|
||||
async-trait.workspace = true
|
||||
bollard.workspace = true
|
||||
chrono.workspace = true
|
||||
dirs.workspace = true
|
||||
futures.workspace = true
|
||||
futures-util.workspace = true
|
||||
lazy_static.workspace = true
|
||||
num_cpus.workspace = true
|
||||
once_cell.workspace = true
|
||||
regex.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
tar.workspace = true
|
||||
tempfile.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
uuid.workspace = true
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::parser::workflow::WorkflowDefinition;
|
||||
use parser::workflow::WorkflowDefinition;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub fn resolve_dependencies(workflow: &WorkflowDefinition) -> Result<Vec<Vec<String>>, String> {
|
||||
@@ -1,5 +1,3 @@
|
||||
use crate::logging;
|
||||
use crate::runtime::container::{ContainerError, ContainerOutput, ContainerRuntime};
|
||||
use async_trait::async_trait;
|
||||
use bollard::{
|
||||
container::{Config, CreateContainerOptions},
|
||||
@@ -8,10 +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;
|
||||
|
||||
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()));
|
||||
@@ -37,21 +39,35 @@ impl DockerRuntime {
|
||||
#[allow(dead_code)]
|
||||
pub fn get_customized_image(base_image: &str, customization: &str) -> Option<String> {
|
||||
let key = format!("{}:{}", base_image, customization);
|
||||
let images = CUSTOMIZED_IMAGES.lock().unwrap();
|
||||
images.get(&key).cloned()
|
||||
match CUSTOMIZED_IMAGES.lock() {
|
||||
Ok(images) => images.get(&key).cloned(),
|
||||
Err(e) => {
|
||||
logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_customized_image(base_image: &str, customization: &str, new_image: &str) {
|
||||
let key = format!("{}:{}", base_image, customization);
|
||||
let mut images = CUSTOMIZED_IMAGES.lock().unwrap();
|
||||
images.insert(key, new_image.to_string());
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// Find a customized image key by prefix
|
||||
#[allow(dead_code)]
|
||||
pub fn find_customized_image_key(image: &str, prefix: &str) -> Option<String> {
|
||||
let image_keys = CUSTOMIZED_IMAGES.lock().unwrap();
|
||||
let image_keys = match CUSTOMIZED_IMAGES.lock() {
|
||||
Ok(keys) => keys,
|
||||
Err(e) => {
|
||||
logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Look for any key that starts with the prefix
|
||||
for (key, _) in image_keys.iter() {
|
||||
@@ -80,8 +96,13 @@ impl DockerRuntime {
|
||||
(lang, None) => lang.to_string(),
|
||||
};
|
||||
|
||||
let images = CUSTOMIZED_IMAGES.lock().unwrap();
|
||||
images.get(&key).cloned()
|
||||
match CUSTOMIZED_IMAGES.lock() {
|
||||
Ok(images) => images.get(&key).cloned(),
|
||||
Err(e) => {
|
||||
logging::error(&format!("Failed to acquire lock: {}", e));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a customized image with language-specific dependencies
|
||||
@@ -102,8 +123,11 @@ impl DockerRuntime {
|
||||
(lang, None) => lang.to_string(),
|
||||
};
|
||||
|
||||
let mut images = CUSTOMIZED_IMAGES.lock().unwrap();
|
||||
images.insert(key, new_image.to_string());
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare a language-specific environment
|
||||
@@ -250,7 +274,7 @@ pub fn is_available() -> bool {
|
||||
// Spawn a thread with the timeout to prevent blocking the main thread
|
||||
let handle = std::thread::spawn(move || {
|
||||
// Use safe FD redirection utility to suppress Docker error messages
|
||||
match crate::utils::fd::with_stderr_to_null(|| {
|
||||
match fd::with_stderr_to_null(|| {
|
||||
// First, check if docker CLI is available as a quick test
|
||||
if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
|
||||
// Try a simple docker version command with a short timeout
|
||||
@@ -8,14 +8,14 @@ use std::path::Path;
|
||||
use std::process::Command;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::executor::dependency;
|
||||
use crate::executor::docker;
|
||||
use crate::executor::environment;
|
||||
use crate::logging;
|
||||
use crate::matrix::{self, MatrixCombination};
|
||||
use crate::parser::workflow::{parse_workflow, ActionInfo, Job, WorkflowDefinition};
|
||||
use crate::runtime::container::ContainerRuntime;
|
||||
use crate::runtime::emulation::handle_special_action;
|
||||
use crate::dependency;
|
||||
use crate::docker;
|
||||
use crate::environment;
|
||||
use logging;
|
||||
use matrix::MatrixCombination;
|
||||
use parser::workflow::{self, parse_workflow, ActionInfo, Job, WorkflowDefinition};
|
||||
use runtime::container::ContainerRuntime;
|
||||
use runtime::emulation;
|
||||
|
||||
#[allow(unused_variables, unused_assignments)]
|
||||
/// Execute a GitHub Actions workflow file locally
|
||||
@@ -128,15 +128,15 @@ fn initialize_runtime(
|
||||
"Failed to initialize Docker runtime: {}, falling back to emulation mode",
|
||||
e
|
||||
));
|
||||
Ok(Box::new(crate::runtime::emulation::EmulationRuntime::new()))
|
||||
Ok(Box::new(emulation::EmulationRuntime::new()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logging::error("Docker not available, falling back to emulation mode");
|
||||
Ok(Box::new(crate::runtime::emulation::EmulationRuntime::new()))
|
||||
Ok(Box::new(emulation::EmulationRuntime::new()))
|
||||
}
|
||||
}
|
||||
RuntimeType::Emulation => Ok(Box::new(crate::runtime::emulation::EmulationRuntime::new())),
|
||||
RuntimeType::Emulation => Ok(Box::new(emulation::EmulationRuntime::new())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -907,7 +907,7 @@ async fn execute_matrix_job(
|
||||
|
||||
// Before the execute_step function, add this struct
|
||||
struct StepExecutionContext<'a> {
|
||||
step: &'a crate::parser::workflow::Step,
|
||||
step: &'a workflow::Step,
|
||||
step_idx: usize,
|
||||
job_env: &'a HashMap<String, String>,
|
||||
working_dir: &'a Path,
|
||||
@@ -1183,7 +1183,7 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
}
|
||||
} else {
|
||||
// For GitHub actions, check if we have special handling
|
||||
if let Err(e) = handle_special_action(uses).await {
|
||||
if let Err(e) = emulation::handle_special_action(uses).await {
|
||||
// Log error but continue
|
||||
println!(" Warning: Special action handling failed: {}", e);
|
||||
}
|
||||
@@ -1538,9 +1538,11 @@ async fn execute_step(ctx: StepExecutionContext<'_>) -> Result<StepResult, Execu
|
||||
output,
|
||||
}
|
||||
} else {
|
||||
return Err(ExecutionError::Execution(
|
||||
"Step must have either 'uses' or 'run' field".to_string(),
|
||||
));
|
||||
return Ok(StepResult {
|
||||
name: step_name,
|
||||
status: StepStatus::Skipped,
|
||||
output: "Step has neither 'uses' nor 'run'".to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
Ok(step_result)
|
||||
@@ -1730,7 +1732,7 @@ fn extract_language_info(image: &str) -> Option<(&'static str, Option<&str>)> {
|
||||
}
|
||||
|
||||
async fn execute_composite_action(
|
||||
step: &crate::parser::workflow::Step,
|
||||
step: &workflow::Step,
|
||||
action_path: &Path,
|
||||
job_env: &HashMap<String, String>,
|
||||
working_dir: &Path,
|
||||
@@ -1825,7 +1827,7 @@ async fn execute_composite_action(
|
||||
job_env: &action_env,
|
||||
working_dir,
|
||||
runtime,
|
||||
workflow: &crate::parser::workflow::WorkflowDefinition {
|
||||
workflow: &workflow::WorkflowDefinition {
|
||||
name: "Composite Action".to_string(),
|
||||
on: vec![],
|
||||
on_raw: serde_yaml::Value::Null,
|
||||
@@ -1907,9 +1909,7 @@ async fn execute_composite_action(
|
||||
}
|
||||
|
||||
// Helper function to convert YAML step to our Step struct
|
||||
fn convert_yaml_to_step(
|
||||
step_yaml: &serde_yaml::Value,
|
||||
) -> Result<crate::parser::workflow::Step, String> {
|
||||
fn convert_yaml_to_step(step_yaml: &serde_yaml::Value) -> Result<workflow::Step, String> {
|
||||
// Extract step properties
|
||||
let name = step_yaml
|
||||
.get("name")
|
||||
@@ -1961,7 +1961,7 @@ fn convert_yaml_to_step(
|
||||
// Extract continue_on_error
|
||||
let continue_on_error = step_yaml.get("continue-on-error").and_then(|v| v.as_bool());
|
||||
|
||||
Ok(crate::parser::workflow::Step {
|
||||
Ok(workflow::Step {
|
||||
name,
|
||||
uses,
|
||||
run: final_run,
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::matrix::MatrixCombination;
|
||||
use crate::parser::workflow::WorkflowDefinition;
|
||||
use chrono::Utc;
|
||||
use matrix::MatrixCombination;
|
||||
use parser::workflow::WorkflowDefinition;
|
||||
use serde_yaml::Value;
|
||||
use std::{collections::HashMap, fs, io, path::Path};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// executor crate
|
||||
|
||||
#![allow(unused_variables, unused_assignments)]
|
||||
|
||||
pub mod dependency;
|
||||
19
crates/github/Cargo.toml
Normal file
19
crates/github/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "github"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "github functionality for wrkflw"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Add other crate dependencies as needed
|
||||
models = { path = "../models" }
|
||||
|
||||
# External dependencies from workspace
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
serde_json.workspace = true
|
||||
reqwest.workspace = true
|
||||
thiserror.workspace = true
|
||||
lazy_static.workspace = true
|
||||
regex.workspace = true
|
||||
@@ -1,6 +1,9 @@
|
||||
// github crate
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use reqwest::header;
|
||||
use serde_json::{self};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
20
crates/gitlab/Cargo.toml
Normal file
20
crates/gitlab/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "gitlab"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "gitlab functionality for wrkflw"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
|
||||
# External dependencies
|
||||
lazy_static.workspace = true
|
||||
regex.workspace = true
|
||||
reqwest.workspace = true
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
serde_json.workspace = true
|
||||
thiserror.workspace = true
|
||||
urlencoding.workspace = true
|
||||
@@ -1,3 +1,5 @@
|
||||
// gitlab crate
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use reqwest::header;
|
||||
16
crates/logging/Cargo.toml
Normal file
16
crates/logging/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "logging"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "logging functionality for wrkflw"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
|
||||
# External dependencies
|
||||
chrono.workspace = true
|
||||
once_cell.workspace = true
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
@@ -1,3 +1,5 @@
|
||||
|
||||
|
||||
use chrono::Local;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::sync::{Arc, Mutex};
|
||||
16
crates/matrix/Cargo.toml
Normal file
16
crates/matrix/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "matrix"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "matrix functionality for wrkflw"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
|
||||
# External dependencies
|
||||
indexmap.workspace = true
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
thiserror.workspace = true
|
||||
@@ -1,3 +1,5 @@
|
||||
// matrix crate
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml::Value;
|
||||
12
crates/models/Cargo.toml
Normal file
12
crates/models/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "models"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Data models for wrkflw"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
serde_json.workspace = true
|
||||
thiserror.workspace = true
|
||||
17
crates/parser/Cargo.toml
Normal file
17
crates/parser/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "parser"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Parser functionality for wrkflw"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
matrix = { path = "../matrix" }
|
||||
|
||||
# External dependencies
|
||||
jsonschema.workspace = true
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
serde_json.workspace = true
|
||||
@@ -1,2 +1,4 @@
|
||||
// parser crate
|
||||
|
||||
pub mod schema;
|
||||
pub mod workflow;
|
||||
@@ -3,7 +3,7 @@ use serde_json::Value;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
const GITHUB_WORKFLOW_SCHEMA: &str = include_str!("../../schemas/github-workflow.json");
|
||||
const GITHUB_WORKFLOW_SCHEMA: &str = include_str!("../../../schemas/github-workflow.json");
|
||||
|
||||
pub struct SchemaValidator {
|
||||
schema: JSONSchema,
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::matrix::MatrixConfig;
|
||||
use matrix::MatrixConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
19
crates/runtime/Cargo.toml
Normal file
19
crates/runtime/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "runtime"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Runtime environment for wrkflw"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
logging = { path = "../logging" }
|
||||
|
||||
# External dependencies
|
||||
async-trait.workspace = true
|
||||
once_cell.workspace = true
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
tempfile.workspace = true
|
||||
tokio.workspace = true
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::logging;
|
||||
use crate::runtime::container::{ContainerError, ContainerOutput, ContainerRuntime};
|
||||
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;
|
||||
@@ -1,2 +1,4 @@
|
||||
// runtime crate
|
||||
|
||||
pub mod container;
|
||||
pub mod emulation;
|
||||
27
crates/ui/Cargo.toml
Normal file
27
crates/ui/Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "ui"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "user interface functionality for wrkflw"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
evaluator = { path = "../evaluator" }
|
||||
executor = { path = "../executor" }
|
||||
logging = { path = "../logging" }
|
||||
utils = { path = "../utils" }
|
||||
github = { path = "../github" }
|
||||
|
||||
# External dependencies
|
||||
chrono.workspace = true
|
||||
crossterm.workspace = true
|
||||
ratatui.workspace = true
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
tokio.workspace = true
|
||||
serde_json.workspace = true
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
regex.workspace = true
|
||||
futures.workspace = true
|
||||
@@ -1,14 +1,13 @@
|
||||
use crate::evaluator::evaluate_workflow_file;
|
||||
use crate::executor::{self, JobStatus, RuntimeType, StepStatus};
|
||||
use crate::logging;
|
||||
use crate::utils;
|
||||
use crate::utils::is_workflow_file;
|
||||
// ui crate
|
||||
|
||||
use chrono::Local;
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use evaluator::evaluate_workflow_file;
|
||||
use executor::{self, JobStatus, RuntimeType, StepStatus};
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
@@ -25,6 +24,7 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use utils::is_workflow_file;
|
||||
|
||||
// Represents an individual workflow file
|
||||
struct Workflow {
|
||||
@@ -743,7 +743,7 @@ impl App {
|
||||
for log in &self.logs {
|
||||
all_logs.push(log.clone());
|
||||
}
|
||||
for log in crate::logging::get_logs() {
|
||||
for log in logging::get_logs() {
|
||||
all_logs.push(log.clone());
|
||||
}
|
||||
|
||||
@@ -835,7 +835,7 @@ impl App {
|
||||
// Scroll logs down
|
||||
fn scroll_logs_down(&mut self) {
|
||||
// Get total log count including system logs
|
||||
let total_logs = self.logs.len() + crate::logging::get_logs().len();
|
||||
let total_logs = self.logs.len() + logging::get_logs().len();
|
||||
if total_logs > 0 {
|
||||
self.log_scroll = (self.log_scroll + 1).min(total_logs - 1);
|
||||
}
|
||||
@@ -2135,7 +2135,7 @@ fn render_logs_tab(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &App, area:
|
||||
}
|
||||
|
||||
// Process system logs
|
||||
for log in crate::logging::get_logs() {
|
||||
for log in logging::get_logs() {
|
||||
all_logs.push(log.clone());
|
||||
}
|
||||
|
||||
@@ -2335,7 +2335,7 @@ fn render_logs_tab(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &App, area:
|
||||
} else {
|
||||
// This is a system log
|
||||
let system_idx = original_idx - app.logs.len();
|
||||
crate::logging::get_logs()
|
||||
logging::get_logs()
|
||||
.get(system_idx)
|
||||
.map(|l| all_logs.iter().position(|al| al == l))
|
||||
};
|
||||
@@ -2741,7 +2741,7 @@ fn render_status_bar(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &App, are
|
||||
}
|
||||
2 => {
|
||||
// For logs tab, show scrolling instructions
|
||||
let log_count = app.logs.len() + crate::logging::get_logs().len();
|
||||
let log_count = app.logs.len() + logging::get_logs().len();
|
||||
if log_count > 0 {
|
||||
// Convert to a static string for consistent return type
|
||||
let scroll_text = format!(
|
||||
@@ -3300,8 +3300,8 @@ async fn execute_curl_trigger(
|
||||
}
|
||||
|
||||
// Get repository information
|
||||
let repo_info = crate::github::get_repo_info()
|
||||
.map_err(|e| format!("Failed to get repository info: {}", e))?;
|
||||
let repo_info =
|
||||
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);
|
||||
15
crates/utils/Cargo.toml
Normal file
15
crates/utils/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "utils"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "utility functions for wrkflw"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
|
||||
# External dependencies
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
nix.workspace = true
|
||||
@@ -1,3 +1,5 @@
|
||||
// utils crate
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
pub fn is_workflow_file(path: &Path) -> bool {
|
||||
15
crates/validators/Cargo.toml
Normal file
15
crates/validators/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "validators"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "validation functionality for wrkflw"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Internal crates
|
||||
models = { path = "../models" }
|
||||
matrix = { path = "../matrix" }
|
||||
|
||||
# External dependencies
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::models::ValidationResult;
|
||||
use models::ValidationResult;
|
||||
|
||||
pub fn validate_action_reference(
|
||||
action_ref: &str,
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::models::ValidationResult;
|
||||
use crate::validators::{validate_matrix, validate_steps};
|
||||
use crate::{validate_matrix, validate_steps};
|
||||
use models::ValidationResult;
|
||||
use serde_yaml::Value;
|
||||
|
||||
pub fn validate_jobs(jobs: &Value, result: &mut ValidationResult) {
|
||||
@@ -1,3 +1,5 @@
|
||||
// validators crate
|
||||
|
||||
mod actions;
|
||||
mod jobs;
|
||||
mod matrix;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::models::ValidationResult;
|
||||
use models::ValidationResult;
|
||||
use serde_yaml::Value;
|
||||
|
||||
pub fn validate_matrix(matrix: &Value, result: &mut ValidationResult) {
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::models::ValidationResult;
|
||||
use crate::validators::validate_action_reference;
|
||||
use crate::validate_action_reference;
|
||||
use models::ValidationResult;
|
||||
use serde_yaml::Value;
|
||||
|
||||
pub fn validate_steps(steps: &[Value], job_name: &str, result: &mut ValidationResult) {
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::models::ValidationResult;
|
||||
use models::ValidationResult;
|
||||
use serde_yaml::Value;
|
||||
|
||||
pub fn validate_triggers(on: &Value, result: &mut ValidationResult) {
|
||||
60
crates/wrkflw/Cargo.toml
Normal file
60
crates/wrkflw/Cargo.toml
Normal file
@@ -0,0 +1,60 @@
|
||||
[package]
|
||||
name = "wrkflw"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
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" }
|
||||
|
||||
# External dependencies
|
||||
clap.workspace = true
|
||||
bollard.workspace = true
|
||||
tokio.workspace = true
|
||||
futures-util.workspace = true
|
||||
futures.workspace = true
|
||||
chrono.workspace = true
|
||||
uuid.workspace = true
|
||||
tempfile.workspace = true
|
||||
dirs.workspace = true
|
||||
thiserror.workspace = true
|
||||
log.workspace = true
|
||||
regex.workspace = true
|
||||
lazy_static.workspace = true
|
||||
reqwest.workspace = true
|
||||
libc.workspace = true
|
||||
nix.workspace = true
|
||||
urlencoding.workspace = true
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
serde_json.workspace = true
|
||||
colored.workspace = true
|
||||
indexmap.workspace = true
|
||||
rayon.workspace = true
|
||||
num_cpus.workspace = true
|
||||
itertools.workspace = true
|
||||
once_cell.workspace = true
|
||||
crossterm.workspace = true
|
||||
ratatui.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "wrkflw"
|
||||
path = "src/main.rs"
|
||||
470
crates/wrkflw/src/main.rs
Normal file
470
crates/wrkflw/src/main.rs
Normal file
@@ -0,0 +1,470 @@
|
||||
use bollard::Docker;
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
name = "wrkflw",
|
||||
about = "GitHub Workflow validator and executor",
|
||||
version,
|
||||
long_about = "A GitHub Workflow validator and executor that runs workflows locally.\n\nExamples:\n wrkflw validate # Validate all workflows in .github/workflows\n wrkflw run .github/workflows/build.yml # Run a specific workflow\n wrkflw --verbose run .github/workflows/build.yml # Run with more output\n wrkflw --debug run .github/workflows/build.yml # Run with detailed debug information\n wrkflw run --emulate .github/workflows/build.yml # Use emulation mode instead of Docker"
|
||||
)]
|
||||
struct Wrkflw {
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
|
||||
/// Run in verbose mode with detailed output
|
||||
#[arg(short, long, global = true)]
|
||||
verbose: bool,
|
||||
|
||||
/// Run in debug mode with extensive execution details
|
||||
#[arg(short, long, global = true)]
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Commands {
|
||||
/// Validate GitHub workflow files
|
||||
Validate {
|
||||
/// Path to workflow file or directory (defaults to .github/workflows)
|
||||
path: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Execute GitHub workflow files locally
|
||||
Run {
|
||||
/// Path to workflow file to execute
|
||||
path: PathBuf,
|
||||
|
||||
/// Use emulation mode instead of Docker
|
||||
#[arg(short, long)]
|
||||
emulate: bool,
|
||||
|
||||
/// Show 'Would execute GitHub action' messages in emulation mode
|
||||
#[arg(long, default_value_t = false)]
|
||||
show_action_messages: bool,
|
||||
},
|
||||
|
||||
/// Open TUI interface to manage workflows
|
||||
Tui {
|
||||
/// Path to workflow file or directory (defaults to .github/workflows)
|
||||
path: Option<PathBuf>,
|
||||
|
||||
/// Use emulation mode instead of Docker
|
||||
#[arg(short, long)]
|
||||
emulate: bool,
|
||||
|
||||
/// Show 'Would execute GitHub action' messages in emulation mode
|
||||
#[arg(long, default_value_t = false)]
|
||||
show_action_messages: bool,
|
||||
},
|
||||
|
||||
/// Trigger a GitHub workflow remotely
|
||||
Trigger {
|
||||
/// Name of the workflow file (without .yml extension)
|
||||
workflow: String,
|
||||
|
||||
/// Branch to run the workflow on
|
||||
#[arg(short, long)]
|
||||
branch: Option<String>,
|
||||
|
||||
/// Key-value inputs for the workflow in format key=value
|
||||
#[arg(short, long, value_parser = parse_key_val)]
|
||||
input: Option<Vec<(String, String)>>,
|
||||
},
|
||||
|
||||
/// Trigger a GitLab pipeline remotely
|
||||
TriggerGitlab {
|
||||
/// Branch to run the pipeline on
|
||||
#[arg(short, long)]
|
||||
branch: Option<String>,
|
||||
|
||||
/// Key-value variables for the pipeline in format key=value
|
||||
#[arg(short = 'V', long, value_parser = parse_key_val)]
|
||||
variable: Option<Vec<(String, String)>>,
|
||||
},
|
||||
|
||||
/// List available workflows
|
||||
List,
|
||||
}
|
||||
|
||||
// Parser function for key-value pairs
|
||||
fn parse_key_val(s: &str) -> Result<(String, String), String> {
|
||||
let pos = s
|
||||
.find('=')
|
||||
.ok_or_else(|| format!("invalid KEY=value: no `=` found in `{}`", s))?;
|
||||
|
||||
Ok((s[..pos].to_string(), s[pos + 1..].to_string()))
|
||||
}
|
||||
|
||||
/// Clean up all resources when exiting the application
|
||||
/// This is used by both main.rs and in tests
|
||||
pub async fn cleanup_on_exit() {
|
||||
// Clean up Docker resources if available, but don't let it block indefinitely
|
||||
match tokio::time::timeout(std::time::Duration::from_secs(3), async {
|
||||
match Docker::connect_with_local_defaults() {
|
||||
Ok(docker) => {
|
||||
let _ = executor::docker::cleanup_containers(&docker).await;
|
||||
}
|
||||
Err(_) => {
|
||||
// Docker not available
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// Always clean up emulation resources
|
||||
match tokio::time::timeout(
|
||||
std::time::Duration::from_secs(2),
|
||||
runtime::emulation::cleanup_resources(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => logging::debug("Emulation cleanup completed successfully"),
|
||||
Err(_) => logging::warning("Emulation cleanup timed out, continuing with shutdown"),
|
||||
}
|
||||
|
||||
logging::info("Resource cleanup completed");
|
||||
}
|
||||
|
||||
async fn handle_signals() {
|
||||
// Set up a hard exit timer in case cleanup takes too long
|
||||
// This ensures the app always exits even if Docker operations are stuck
|
||||
let hard_exit_time = std::time::Duration::from_secs(10);
|
||||
|
||||
// Wait for Ctrl+C
|
||||
match tokio::signal::ctrl_c().await {
|
||||
Ok(_) => {
|
||||
println!("Received Ctrl+C, shutting down and cleaning up...");
|
||||
}
|
||||
Err(e) => {
|
||||
// Log the error but continue with cleanup
|
||||
eprintln!("Warning: Failed to properly listen for ctrl+c event: {}", e);
|
||||
println!("Shutting down and cleaning up...");
|
||||
}
|
||||
}
|
||||
|
||||
// Set up a watchdog thread that will force exit if cleanup takes too long
|
||||
// This is important because Docker operations can sometimes hang indefinitely
|
||||
let _ = std::thread::spawn(move || {
|
||||
std::thread::sleep(hard_exit_time);
|
||||
eprintln!(
|
||||
"Cleanup taking too long (over {} seconds), forcing exit...",
|
||||
hard_exit_time.as_secs()
|
||||
);
|
||||
logging::error("Forced exit due to cleanup timeout");
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
// Clean up containers
|
||||
cleanup_on_exit().await;
|
||||
|
||||
// Exit with success status - the force exit thread will be terminated automatically
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let cli = Wrkflw::parse();
|
||||
let verbose = cli.verbose;
|
||||
let debug = cli.debug;
|
||||
|
||||
// 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");
|
||||
} else if verbose {
|
||||
logging::set_log_level(logging::LogLevel::Info);
|
||||
logging::info("Verbose mode enabled");
|
||||
} else {
|
||||
logging::set_log_level(logging::LogLevel::Warning);
|
||||
}
|
||||
|
||||
// Setup a Ctrl+C handler that runs in the background
|
||||
tokio::spawn(handle_signals());
|
||||
|
||||
match &cli.command {
|
||||
Some(Commands::Validate { path }) => {
|
||||
// Determine the path to validate
|
||||
let validate_path = path
|
||||
.clone()
|
||||
.unwrap_or_else(|| PathBuf::from(".github/workflows"));
|
||||
|
||||
// Run the validation
|
||||
ui::validate_workflow(&validate_path, verbose).unwrap_or_else(|e| {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
Some(Commands::Run {
|
||||
path,
|
||||
emulate,
|
||||
show_action_messages: _,
|
||||
}) => {
|
||||
// Set runner mode based on flags
|
||||
let runtime_type = if *emulate {
|
||||
executor::RuntimeType::Emulation
|
||||
} else {
|
||||
executor::RuntimeType::Docker
|
||||
};
|
||||
|
||||
// First validate the workflow file
|
||||
match parser::workflow::parse_workflow(path) {
|
||||
Ok(_) => logging::info("Validating workflow..."),
|
||||
Err(e) => {
|
||||
logging::error(&format!("Workflow validation failed: {}", e));
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the workflow
|
||||
match executor::execute_workflow(path, runtime_type, verbose || debug).await {
|
||||
Ok(result) => {
|
||||
// Print job results
|
||||
for job in &result.jobs {
|
||||
println!(
|
||||
"\n{} Job {}: {}",
|
||||
if job.status == executor::JobStatus::Success {
|
||||
"✅"
|
||||
} else {
|
||||
"❌"
|
||||
},
|
||||
job.name,
|
||||
if job.status == executor::JobStatus::Success {
|
||||
"succeeded"
|
||||
} else {
|
||||
"failed"
|
||||
}
|
||||
);
|
||||
|
||||
// Print step results
|
||||
for step in &job.steps {
|
||||
println!(
|
||||
" {} {}",
|
||||
if step.status == executor::StepStatus::Success {
|
||||
"✅"
|
||||
} else {
|
||||
"❌"
|
||||
},
|
||||
step.name
|
||||
);
|
||||
|
||||
if !step.output.trim().is_empty() {
|
||||
// If the output is very long, trim it
|
||||
let output_lines = step.output.lines().collect::<Vec<&str>>();
|
||||
|
||||
println!(" Output:");
|
||||
|
||||
// In verbose mode, show complete output
|
||||
if verbose || debug {
|
||||
for line in &output_lines {
|
||||
println!(" {}", line);
|
||||
}
|
||||
} else {
|
||||
// Show only the first few lines
|
||||
let max_lines = 5;
|
||||
for line in output_lines.iter().take(max_lines) {
|
||||
println!(" {}", line);
|
||||
}
|
||||
|
||||
if output_lines.len() > max_lines {
|
||||
println!(" ... ({} more lines, use --verbose to see full output)",
|
||||
output_lines.len() - max_lines);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print detailed failure information if available
|
||||
if let Some(failure_details) = &result.failure_details {
|
||||
println!("\n❌ Workflow execution failed!");
|
||||
println!("{}", failure_details);
|
||||
println!("\nTo fix these issues:");
|
||||
println!("1. Check the formatting issues with: cargo fmt");
|
||||
println!("2. Fix clippy warnings with: cargo clippy -- -D warnings");
|
||||
println!("3. Run tests to ensure everything passes: cargo test");
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
println!("\n✅ Workflow completed successfully!");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
logging::error(&format!("Workflow execution failed: {}", e));
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Commands::Tui {
|
||||
path,
|
||||
emulate,
|
||||
show_action_messages,
|
||||
}) => {
|
||||
// Open the TUI interface
|
||||
let runtime_type = if *emulate {
|
||||
executor::RuntimeType::Emulation
|
||||
} else {
|
||||
// Check if Docker is available, fall back to emulation if not
|
||||
if !executor::docker::is_available() {
|
||||
println!("⚠️ Docker is not available. Using emulation mode instead.");
|
||||
logging::warning("Docker is not available. Using emulation mode instead.");
|
||||
executor::RuntimeType::Emulation
|
||||
} else {
|
||||
executor::RuntimeType::Docker
|
||||
}
|
||||
};
|
||||
|
||||
// Control hiding action messages based on the flag
|
||||
if !show_action_messages {
|
||||
std::env::set_var("WRKFLW_HIDE_ACTION_MESSAGES", "true");
|
||||
} else {
|
||||
std::env::set_var("WRKFLW_HIDE_ACTION_MESSAGES", "false");
|
||||
}
|
||||
|
||||
match ui::run_wrkflw_tui(path.as_ref(), runtime_type, verbose).await {
|
||||
Ok(_) => {
|
||||
// Clean up on successful exit
|
||||
cleanup_on_exit().await;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
cleanup_on_exit().await;
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Commands::Trigger {
|
||||
workflow,
|
||||
branch,
|
||||
input,
|
||||
}) => {
|
||||
logging::info(&format!("Triggering workflow {} on GitHub", workflow));
|
||||
|
||||
// Convert inputs to HashMap
|
||||
let input_map = input.as_ref().map(|i| {
|
||||
i.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect::<HashMap<String, String>>()
|
||||
});
|
||||
|
||||
match github::trigger_workflow(workflow, branch.as_deref(), input_map).await {
|
||||
Ok(_) => logging::info("Workflow triggered successfully"),
|
||||
Err(e) => {
|
||||
eprintln!("Error triggering workflow: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Commands::TriggerGitlab { branch, variable }) => {
|
||||
logging::info("Triggering pipeline on GitLab");
|
||||
|
||||
// Convert variables to HashMap
|
||||
let variable_map = variable.as_ref().map(|v| {
|
||||
v.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect::<HashMap<String, String>>()
|
||||
});
|
||||
|
||||
match gitlab::trigger_pipeline(branch.as_deref(), variable_map).await {
|
||||
Ok(_) => logging::info("GitLab pipeline triggered successfully"),
|
||||
Err(e) => {
|
||||
eprintln!("Error triggering GitLab pipeline: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(Commands::List) => {
|
||||
logging::info("Listing available workflows");
|
||||
|
||||
// Attempt to get GitHub repo info
|
||||
if let Ok(repo_info) = github::get_repo_info() {
|
||||
match github::list_workflows(&repo_info).await {
|
||||
Ok(workflows) => {
|
||||
if workflows.is_empty() {
|
||||
println!("No GitHub workflows found in repository");
|
||||
} else {
|
||||
println!("GitHub workflows:");
|
||||
for workflow in workflows {
|
||||
println!(" {}", workflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error listing GitHub workflows: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Not a GitHub repository or unable to get repository information");
|
||||
}
|
||||
|
||||
// Attempt to get GitLab repo info
|
||||
if let Ok(repo_info) = gitlab::get_repo_info() {
|
||||
match gitlab::list_pipelines(&repo_info).await {
|
||||
Ok(pipelines) => {
|
||||
if pipelines.is_empty() {
|
||||
println!("No GitLab pipelines found in repository");
|
||||
} else {
|
||||
println!("GitLab pipelines:");
|
||||
for pipeline in pipelines {
|
||||
println!(" {}", pipeline);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error listing GitLab pipelines: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Not a GitLab repository or unable to get repository information");
|
||||
}
|
||||
}
|
||||
|
||||
None => {
|
||||
// Default to TUI interface if no subcommand
|
||||
// Check if Docker is available, fall back to emulation if not
|
||||
let runtime_type = if !executor::docker::is_available() {
|
||||
println!("⚠️ Docker is not available. Using emulation mode instead.");
|
||||
logging::warning("Docker is not available. Using emulation mode instead.");
|
||||
executor::RuntimeType::Emulation
|
||||
} else {
|
||||
executor::RuntimeType::Docker
|
||||
};
|
||||
|
||||
// Set environment variable to hide action messages by default
|
||||
std::env::set_var("WRKFLW_HIDE_ACTION_MESSAGES", "true");
|
||||
|
||||
match ui::run_wrkflw_tui(
|
||||
Some(&PathBuf::from(".github/workflows")),
|
||||
runtime_type,
|
||||
verbose,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
// Clean up on successful exit
|
||||
cleanup_on_exit().await;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
cleanup_on_exit().await;
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Final cleanup before program exit
|
||||
cleanup_on_exit().await;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#[async_trait]
|
||||
impl ContainerRuntime for EmulationRuntime {
|
||||
async fn run_container(
|
||||
&self,
|
||||
image: &str,
|
||||
cmd: &[&str],
|
||||
env_vars: &[(&str, &str)],
|
||||
working_dir: &Path,
|
||||
volumes: &[(&Path, &Path)],
|
||||
) -> Result<ContainerOutput, ContainerError> {
|
||||
// ... existing code ...
|
||||
}
|
||||
|
||||
async fn pull_image(&self, image: &str) -> Result<(), ContainerError> {
|
||||
// ... existing code ...
|
||||
}
|
||||
|
||||
async fn build_image(&self, dockerfile: &Path, tag: &str) -> Result<(), ContainerError> {
|
||||
// ... existing code ...
|
||||
}
|
||||
|
||||
async fn prepare_language_environment(
|
||||
&self,
|
||||
language: &str,
|
||||
version: Option<&str>,
|
||||
additional_packages: Option<Vec<String>>,
|
||||
) -> Result<String, ContainerError> {
|
||||
// For emulation runtime, we'll use a simplified approach
|
||||
// that doesn't require building custom images
|
||||
let base_image = match language {
|
||||
"python" => version.map_or("python:3.11-slim".to_string(), |v| format!("python:{}", v)),
|
||||
"node" => version.map_or("node:20-slim".to_string(), |v| format!("node:{}", v)),
|
||||
"java" => version.map_or("eclipse-temurin:17-jdk".to_string(), |v| format!("eclipse-temurin:{}", v)),
|
||||
"go" => version.map_or("golang:1.21-slim".to_string(), |v| format!("golang:{}", v)),
|
||||
"dotnet" => version.map_or("mcr.microsoft.com/dotnet/sdk:7.0".to_string(), |v| format!("mcr.microsoft.com/dotnet/sdk:{}", v)),
|
||||
"rust" => version.map_or("rust:latest".to_string(), |v| format!("rust:{}", v)),
|
||||
_ => return Err(ContainerError::ContainerStart(format!("Unsupported language: {}", language))),
|
||||
};
|
||||
|
||||
// For emulation, we'll just return the base image
|
||||
// The actual package installation will be handled during container execution
|
||||
Ok(base_image)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user