mirror of
https://github.com/bahdotsh/wrkflw.git
synced 2026-02-24 03:49:45 +01:00
Merge pull request #27 from bahdotsh/bahdotsh/validation-exit-codes
feat: add exit code support for validation failures
This commit is contained in:
58
README.md
58
README.md
@@ -13,7 +13,7 @@ WRKFLW is a powerful command-line tool for validating and executing GitHub Actio
|
||||
## Features
|
||||
|
||||
- **TUI Interface**: A full-featured terminal user interface for managing and monitoring workflow executions
|
||||
- **Validate Workflow Files**: Check for syntax errors and common mistakes in GitHub Actions workflow files
|
||||
- **Validate Workflow Files**: Check for syntax errors and common mistakes in GitHub Actions workflow files with proper exit codes for CI/CD integration
|
||||
- **Execute Workflows Locally**: Run workflows directly on your machine using Docker containers
|
||||
- **Emulation Mode**: Optional execution without Docker by emulating the container environment locally
|
||||
- **Job Dependency Resolution**: Automatically determines the correct execution order based on job dependencies
|
||||
@@ -77,8 +77,38 @@ wrkflw validate path/to/workflows
|
||||
|
||||
# Validate with verbose output
|
||||
wrkflw validate --verbose path/to/workflow.yml
|
||||
|
||||
# Validate GitLab CI pipelines
|
||||
wrkflw validate .gitlab-ci.yml --gitlab
|
||||
|
||||
# Disable exit codes for custom error handling (default: enabled)
|
||||
wrkflw validate --no-exit-code path/to/workflow.yml
|
||||
```
|
||||
|
||||
#### Exit Codes for CI/CD Integration
|
||||
|
||||
By default, `wrkflw validate` sets the exit code to `1` when validation fails, making it perfect for CI/CD pipelines and scripts:
|
||||
|
||||
```bash
|
||||
# In CI/CD scripts - validation failure will cause the script to exit
|
||||
if ! wrkflw validate; then
|
||||
echo "❌ Workflow validation failed!"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ All workflows are valid!"
|
||||
|
||||
# For custom error handling, disable exit codes
|
||||
wrkflw validate --no-exit-code
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Validation completed (check output for details)"
|
||||
fi
|
||||
```
|
||||
|
||||
**Exit Code Behavior:**
|
||||
- `0`: All validations passed successfully
|
||||
- `1`: One or more validation failures detected
|
||||
- `2`: Command usage error (invalid arguments, file not found, etc.)
|
||||
|
||||
### Running Workflows in CLI Mode
|
||||
|
||||
```bash
|
||||
@@ -143,17 +173,25 @@ The terminal user interface provides an interactive way to manage workflows:
|
||||
|
||||
```bash
|
||||
$ wrkflw validate .github/workflows/rust.yml
|
||||
Validating GitHub workflow file: .github/workflows/rust.yml... Validating 1 workflow file(s)...
|
||||
✅ Valid: .github/workflows/rust.yml
|
||||
|
||||
Validating workflows in: .github/workflows/rust.yml
|
||||
============================================================
|
||||
✅ Valid: rust.yml
|
||||
------------------------------------------------------------
|
||||
Summary: 1 valid, 0 invalid
|
||||
|
||||
Summary
|
||||
============================================================
|
||||
✅ 1 valid workflow file(s)
|
||||
$ echo $?
|
||||
0
|
||||
|
||||
All workflows are valid! 🎉
|
||||
# Example with validation failure
|
||||
$ wrkflw validate .github/workflows/invalid.yml
|
||||
Validating GitHub workflow file: .github/workflows/invalid.yml... Validating 1 workflow file(s)...
|
||||
❌ Invalid: .github/workflows/invalid.yml
|
||||
1. Job 'test' is missing 'runs-on' field
|
||||
2. Job 'test' is missing 'steps' section
|
||||
|
||||
Summary: 0 valid, 1 invalid
|
||||
|
||||
$ echo $?
|
||||
1
|
||||
```
|
||||
|
||||
### Running a Workflow
|
||||
@@ -246,7 +284,7 @@ This allows you to inspect the exact state of the container when the failure occ
|
||||
## Limitations
|
||||
|
||||
### Supported Features
|
||||
- ✅ Basic workflow syntax and validation (all YAML syntax checks, required fields, and structure)
|
||||
- ✅ Basic workflow syntax and validation (all YAML syntax checks, required fields, and structure) with proper exit codes for CI/CD integration
|
||||
- ✅ Job dependency resolution and parallel execution (all jobs with correct 'needs' relationships are executed in the right order, and independent jobs run in parallel)
|
||||
- ✅ Matrix builds (supported for reasonable matrix sizes; very large matrices may be slow or resource-intensive)
|
||||
- ✅ Environment variables and GitHub context (all standard GitHub Actions environment variables and context objects are emulated)
|
||||
|
||||
@@ -34,6 +34,14 @@ enum Commands {
|
||||
/// Explicitly validate as GitLab CI/CD pipeline
|
||||
#[arg(long)]
|
||||
gitlab: bool,
|
||||
|
||||
/// Set exit code to 1 on validation failure
|
||||
#[arg(long = "exit-code", default_value_t = true)]
|
||||
exit_code: bool,
|
||||
|
||||
/// Don't set exit code to 1 on validation failure (overrides --exit-code)
|
||||
#[arg(long = "no-exit-code", conflicts_with = "exit_code")]
|
||||
no_exit_code: bool,
|
||||
},
|
||||
|
||||
/// Execute workflow or pipeline files locally
|
||||
@@ -257,7 +265,12 @@ async fn main() {
|
||||
tokio::spawn(handle_signals());
|
||||
|
||||
match &cli.command {
|
||||
Some(Commands::Validate { path, gitlab }) => {
|
||||
Some(Commands::Validate {
|
||||
path,
|
||||
gitlab,
|
||||
exit_code,
|
||||
no_exit_code,
|
||||
}) => {
|
||||
// Determine the path to validate
|
||||
let validate_path = path
|
||||
.clone()
|
||||
@@ -271,6 +284,7 @@ async fn main() {
|
||||
|
||||
// Determine if we're validating a GitLab pipeline based on the --gitlab flag or file detection
|
||||
let force_gitlab = *gitlab;
|
||||
let mut validation_failed = false;
|
||||
|
||||
if validate_path.is_dir() {
|
||||
// Validate all workflow files in the directory
|
||||
@@ -292,21 +306,30 @@ async fn main() {
|
||||
let path = entry.path();
|
||||
let is_gitlab = force_gitlab || is_gitlab_pipeline(&path);
|
||||
|
||||
if is_gitlab {
|
||||
validate_gitlab_pipeline(&path, verbose);
|
||||
let file_failed = if is_gitlab {
|
||||
validate_gitlab_pipeline(&path, verbose)
|
||||
} else {
|
||||
validate_github_workflow(&path, verbose);
|
||||
validate_github_workflow(&path, verbose)
|
||||
};
|
||||
|
||||
if file_failed {
|
||||
validation_failed = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Validate a single workflow file
|
||||
let is_gitlab = force_gitlab || is_gitlab_pipeline(&validate_path);
|
||||
|
||||
if is_gitlab {
|
||||
validate_gitlab_pipeline(&validate_path, verbose);
|
||||
validation_failed = if is_gitlab {
|
||||
validate_gitlab_pipeline(&validate_path, verbose)
|
||||
} else {
|
||||
validate_github_workflow(&validate_path, verbose);
|
||||
}
|
||||
validate_github_workflow(&validate_path, verbose)
|
||||
};
|
||||
}
|
||||
|
||||
// Set exit code if validation failed and exit_code flag is true (and no_exit_code is false)
|
||||
if validation_failed && *exit_code && !*no_exit_code {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
Some(Commands::Run {
|
||||
@@ -507,22 +530,32 @@ async fn main() {
|
||||
}
|
||||
|
||||
/// Validate a GitHub workflow file
|
||||
fn validate_github_workflow(path: &Path, verbose: bool) {
|
||||
/// Returns true if validation failed, false if it passed
|
||||
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) {
|
||||
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,
|
||||
// we need to call the evaluator directly to get the result
|
||||
match evaluator::evaluate_workflow_file(path, verbose) {
|
||||
Ok(result) => !result.is_valid,
|
||||
Err(_) => true, // Parse errors count as validation failure
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error validating workflow: {}", e);
|
||||
true // Any error counts as validation failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate a GitLab CI/CD pipeline file
|
||||
fn validate_gitlab_pipeline(path: &Path, verbose: bool) {
|
||||
/// Returns true if validation failed, false if it passed
|
||||
fn validate_gitlab_pipeline(path: &Path, verbose: bool) -> bool {
|
||||
print!("Validating GitLab CI pipeline file: {}... ", path.display());
|
||||
|
||||
// Parse and validate the pipeline file
|
||||
@@ -538,13 +571,18 @@ fn validate_gitlab_pipeline(path: &Path, verbose: bool) {
|
||||
for issue in validation_result.issues {
|
||||
println!(" - {}", issue);
|
||||
}
|
||||
} else if verbose {
|
||||
println!("✅ All validation checks passed");
|
||||
true // Validation failed
|
||||
} else {
|
||||
if verbose {
|
||||
println!("✅ All validation checks passed");
|
||||
}
|
||||
false // Validation passed
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("❌ Invalid");
|
||||
eprintln!("Validation failed: {}", e);
|
||||
true // Parse error counts as validation failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user