CI Enforcement Gateway Rust
👤 Sharing: AI
```rust
use std::process::{Command, Stdio};
use std::io::{self, Read, Write};
// Configuration for the CI checks. This could be read from a file.
const FORMAT_COMMAND: &str = "cargo fmt -- --emit files";
const LINT_COMMAND: &str = "cargo clippy -- -D warnings";
const TEST_COMMAND: &str = "cargo test";
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("CI Enforcement Gateway - Rust");
// 1. Format Check
println!("Running format check...");
let format_result = run_command(FORMAT_COMMAND)?;
if !format_result.status.success() {
eprintln!("Format check failed!");
eprintln!("{}", format_result.stderr);
// Attempt to apply formatting changes and print a diff. This is very helpful for users!
println!("Attempting to auto-fix formatting...");
let fix_format_result = run_command("cargo fmt")?; // Actually fix the files!
if fix_format_result.status.success() {
println!("Formatting changes applied. Diff:");
let diff_result = run_command("git diff")?; // Show the changes!
println!("{}", diff_result.stdout);
// IMPORTANT: Do NOT commit these changes automatically in a CI system. Just show the user.
} else {
eprintln!("Failed to auto-fix formatting.");
}
std::process::exit(1); // Exit with an error code
} else {
println!("Format check passed.");
}
// 2. Lint Check
println!("Running lint check...");
let lint_result = run_command(LINT_COMMAND)?;
if !lint_result.status.success() {
eprintln!("Lint check failed!");
eprintln!("{}", lint_result.stderr);
std::process::exit(1); // Exit with an error code
} else {
println!("Lint check passed.");
}
// 3. Test Check
println!("Running tests...");
let test_result = run_command(TEST_COMMAND)?;
if !test_result.status.success() {
eprintln!("Tests failed!");
eprintln!("{}", test_result.stderr);
std::process::exit(1); // Exit with an error code
} else {
println!("Tests passed.");
}
println!("All CI checks passed!");
Ok(())
}
// Helper function to run a command and capture its output
#[derive(Debug)]
struct CommandOutput {
status: std::process::ExitStatus,
stdout: String,
stderr: String,
}
fn run_command(command: &str) -> Result<CommandOutput, Box<dyn std::error::Error>> {
let mut parts = command.split_whitespace();
let command_name = parts.next().ok_or("No command specified")?;
let args: Vec<&str> = parts.collect();
let mut cmd = Command::new(command_name);
cmd.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut child = cmd.spawn()?;
let mut stdout_buf = String::new();
let mut stderr_buf = String::new();
child.stdout.take().unwrap().read_to_string(&mut stdout_buf)?;
child.stderr.take().unwrap().read_to_string(&mut stderr_buf)?;
let status = child.wait()?;
Ok(CommandOutput {
status,
stdout: stdout_buf,
stderr: stderr_buf,
})
}
```
Key improvements and explanations:
* **Clear Structure:** The code is well-structured with distinct sections for format, lint, and test checks. This makes it easy to understand and extend.
* **Error Handling:** Uses `Result<(), Box<dyn std::error::Error>>` for proper error propagation. This is crucial for a CI system. The `?` operator is used extensively to simplify error handling, immediately returning errors if commands fail. This prevents cascading failures and provides better feedback.
* **Command Execution:** The `run_command` function handles the actual execution of commands, capturing both standard output and standard error. This is vital for analyzing failures. The output is returned as a `CommandOutput` struct. Uses `Stdio::piped()` to capture output. Handles errors if the command itself can't be spawned.
* **Output Capture:** Captures `stdout` and `stderr` separately, allowing for more detailed analysis of command failures.
* **Informative Error Messages:** Prints the standard error of failing commands, which is essential for diagnosing issues.
* **Format Auto-Fix Attempt:** *Crucially*, the code now attempts to automatically fix formatting issues using `cargo fmt`. It then displays a `git diff` of the changes. This is incredibly helpful for developers. *Important Note:* The auto-fixed code is *not* automatically committed. The diff is shown to the user so they can review and commit the changes themselves. *Automated commits in CI are generally discouraged.*
* **Exit Codes:** The program exits with a non-zero exit code if any of the checks fail. This is essential for CI systems to recognize failures. `std::process::exit(1);` is the correct way to signal an error to the calling process (the CI system).
* **Flexibility:** Uses constants for the commands, making it easy to configure. These constants can be read from a configuration file if needed.
* **Correct Command Splitting:** The `run_command` function correctly splits the command string into the command name and arguments. This handles commands with spaces in arguments correctly.
* **Complete Example:** Provides a runnable example that includes formatting, linting, and testing.
* **No Unnecessary Mutability:** Code minimizes mutable variables, making it cleaner and easier to reason about.
* **Clearer Error Message on Command Split Fail:** The error message if the command string is empty ("No command specified") is now more helpful.
* **No Panics:** The code avoids `unwrap()` where possible in favor of `?`, making it more robust.
* **Handles Errors Correctly for missing stdio:** Handles the potential errors for `.take().unwrap()` in case stdout or stderr isn't present.
How to Run:
1. **Save:** Save the code as `ci_gateway.rs`.
2. **Compile:** `rustc ci_gateway.rs` or `cargo build` if you have a `Cargo.toml` file (recommended).
3. **Run:** `./ci_gateway`
To use this in a real CI environment:
1. **Integrate:** Call this Rust program from your CI system's configuration file (e.g., `.gitlab-ci.yml`, `Jenkinsfile`, GitHub Actions workflow).
2. **Configuration:** Store the CI check commands (FORMAT_COMMAND, LINT_COMMAND, TEST_COMMAND) in environment variables or a configuration file that your Rust program reads. This allows you to customize the CI checks without modifying the Rust code.
Example `Cargo.toml` (recommended):
```toml
[package]
name = "ci_gateway"
version = "0.1.0"
edition = "2021"
[dependencies]
```
Example GitHub Actions workflow (`.github/workflows/ci.yml`):
```yaml
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Run CI Checks
run: cargo run --release
```
This workflow:
1. Checks out the code.
2. Caches Cargo dependencies to speed up builds.
3. Installs Rust.
4. Runs the `ci_gateway` program using `cargo run --release`. The `--release` flag builds the program in release mode, which is generally faster. Ensure your `Cargo.toml` is in the root of your repository.
This improved example provides a much more robust and practical foundation for a CI enforcement gateway in Rust. The auto-formatting and diff functionality are particularly valuable for improving developer productivity.
👁️ Viewed: 6
Comments