ci-enhanced.ymlā¢18.2 kB
name: Enhanced CI/CD Pipeline
on:
push:
branches: [ main, develop ]
tags: [ 'v*' ]
pull_request:
branches: [ main, develop ]
schedule:
# Run security audit daily at 2 AM UTC
- cron: '0 2 * * *'
workflow_dispatch:
inputs:
rust_version:
description: 'Rust version to test'
required: false
default: 'stable'
type: choice
options:
- stable
- beta
- nightly
- '1.70.0'
- '1.75.0'
- '1.80.0'
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
RUSTUP_MAX_RETRIES: 10
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# Pre-check job for basic validation
pre-check:
name: Pre-flight Checks
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
has-changes: ${{ steps.changes.outputs.has-changes }}
should-test: ${{ steps.changes.outputs.should-test }}
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Check for relevant changes
id: changes
run: |
if [[ "${{ github.event_name }}" == "schedule" || "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "has-changes=true" >> $GITHUB_OUTPUT
echo "should-test=true" >> $GITHUB_OUTPUT
exit 0
fi
# Check for Rust/Cargo changes
CHANGED_FILES=$(git diff --name-only ${{ github.event.before }}..${{ github.sha }} 2>/dev/null || git diff --name-only HEAD~1..HEAD)
if echo "$CHANGED_FILES" | grep -qE '\.(rs|toml)$|^\.github/workflows/|Dockerfile|\.dockerignore$'; then
echo "has-changes=true" >> $GITHUB_OUTPUT
echo "should-test=true" >> $GITHUB_OUTPUT
else
echo "has-changes=false" >> $GITHUB_OUTPUT
echo "should-test=false" >> $GITHUB_OUTPUT
fi
- name: Validate workflow files
run: |
# Basic YAML validation
for file in .github/workflows/*.yml .github/workflows/*.yaml; do
if [ -f "$file" ]; then
echo "Validating $file"
python3 -c "import yaml; yaml.safe_load(open('$file'))"
fi
done
# Security audit job
security-audit:
name: Security Audit
runs-on: ubuntu-latest
timeout-minutes: 15
needs: pre-check
if: needs.pre-check.outputs.should-test == 'true'
outputs:
audit-passed: ${{ steps.audit.outcome == 'success' }}
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-audit
run: cargo install cargo-audit --locked
- name: Cache Cargo audit database
uses: actions/cache@v4
with:
path: ~/.cargo/advisory-db
key: cargo-audit-db-${{ github.run_id }}
restore-keys: cargo-audit-db-
- name: Run security audit
id: audit
run: |
echo "Running security audit..."
if ! cargo audit --deny warnings --deny unmaintained --deny unsound --deny yanked; then
echo "Security audit failed! Check the output above for vulnerabilities."
echo "audit-result=failed" >> $GITHUB_OUTPUT
exit 1
else
echo "Security audit passed!"
echo "audit-result=passed" >> $GITHUB_OUTPUT
fi
- name: Generate audit report
if: always()
run: |
cargo audit --json > audit-report.json || true
echo "## Security Audit Report" >> $GITHUB_STEP_SUMMARY
if [ -f audit-report.json ] && [ -s audit-report.json ]; then
echo "```json" >> $GITHUB_STEP_SUMMARY
cat audit-report.json >> $GITHUB_STEP_SUMMARY
echo "```" >> $GITHUB_STEP_SUMMARY
else
echo "No security issues found." >> $GITHUB_STEP_SUMMARY
fi
- name: Upload audit report
if: always()
uses: actions/upload-artifact@v4
with:
name: security-audit-report
path: audit-report.json
retention-days: 30
# Matrix testing job
test-matrix:
name: Test Suite
runs-on: ${{ matrix.os }}
timeout-minutes: 45
needs: [pre-check, security-audit]
if: needs.pre-check.outputs.should-test == 'true'
strategy:
fail-fast: false
matrix:
include:
# Stable Rust on all platforms
- os: ubuntu-latest
rust: stable
target: x86_64-unknown-linux-gnu
features: ''
- os: ubuntu-latest
rust: stable
target: x86_64-unknown-linux-gnu
features: '--all-features'
- os: windows-latest
rust: stable
target: x86_64-pc-windows-msvc
features: ''
- os: macos-latest
rust: stable
target: x86_64-apple-darwin
features: ''
# Beta Rust (Linux only)
- os: ubuntu-latest
rust: beta
target: x86_64-unknown-linux-gnu
features: ''
# Nightly Rust (Linux only, allow failures)
- os: ubuntu-latest
rust: nightly
target: x86_64-unknown-linux-gnu
features: ''
experimental: true
# MSRV (Minimum Supported Rust Version)
- os: ubuntu-latest
rust: '1.70.0'
target: x86_64-unknown-linux-gnu
features: ''
continue-on-error: ${{ matrix.experimental == true }}
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install Rust ${{ matrix.rust }}
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
targets: ${{ matrix.target }}
components: rustfmt, clippy
- name: Install system dependencies (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y build-essential pkg-config libssl-dev protobuf-compiler
- name: Install system dependencies (macOS)
if: matrix.os == 'macos-latest'
run: |
brew install pkg-config protobuf
- name: Install system dependencies (Windows)
if: matrix.os == 'windows-latest'
run: |
choco install protoc
- name: Setup Cargo cache
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-${{ matrix.rust }}-
${{ runner.os }}-cargo-
- name: Check formatting (stable only)
if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.features == ''
run: cargo fmt --all -- --check
- name: Run Clippy
if: matrix.rust == 'stable' || matrix.rust == 'beta'
run: |
if [ -n "${{ matrix.features }}" ]; then
cargo clippy --workspace --all-targets ${{ matrix.features }} --target ${{ matrix.target }} -- -D warnings -W clippy::pedantic
else
cargo clippy --workspace --all-targets --target ${{ matrix.target }} -- -D warnings -W clippy::pedantic
fi
- name: Build
run: |
if [ -n "${{ matrix.features }}" ]; then
cargo build --workspace --target ${{ matrix.target }} ${{ matrix.features }} --verbose
else
cargo build --workspace --target ${{ matrix.target }} --verbose
fi
- name: Run tests
run: |
if [ -n "${{ matrix.features }}" ]; then
cargo test --workspace --target ${{ matrix.target }} ${{ matrix.features }} --verbose
else
cargo test --workspace --target ${{ matrix.target }} --verbose
fi
- name: Run doc tests
if: matrix.target == 'x86_64-unknown-linux-gnu'
run: |
if [ -n "${{ matrix.features }}" ]; then
cargo test --workspace --doc ${{ matrix.features }}
else
cargo test --workspace --doc
fi
# Code coverage job
coverage:
name: Code Coverage
runs-on: ubuntu-latest
timeout-minutes: 30
needs: [pre-check, security-audit]
if: needs.pre-check.outputs.should-test == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential pkg-config libssl-dev protobuf-compiler
- name: Setup Cargo cache
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ubuntu-latest-cargo-coverage-${{ hashFiles('**/Cargo.lock') }}
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Generate code coverage
run: |
cargo llvm-cov --workspace --lcov --output-path lcov.info --ignore-filename-regex "(tests?/|benches?/|examples?/)"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: lcov.info
fail_ci_if_error: false # Don't fail CI on codecov issues
verbose: true
# Advanced linting job
advanced-linting:
name: Advanced Linting
runs-on: ubuntu-latest
timeout-minutes: 20
needs: [pre-check, security-audit]
if: needs.pre-check.outputs.should-test == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Install additional tools
run: |
cargo install cargo-machete cargo-udeps --locked
- name: Setup Cargo cache
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ubuntu-latest-cargo-linting-${{ hashFiles('**/Cargo.lock') }}
- name: Check for unused dependencies
run: |
echo "## Unused Dependencies Check" >> $GITHUB_STEP_SUMMARY
cargo machete --with-metadata || true
- name: Check for unnecessary dependencies (nightly required)
continue-on-error: true
run: |
# Install nightly for cargo-udeps
rustup toolchain install nightly
cargo +nightly udeps --workspace || echo "cargo-udeps check failed (requires nightly features)"
# Build release artifacts
build-release:
name: Build Release
runs-on: ${{ matrix.os }}
timeout-minutes: 45
needs: [test-matrix]
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact_name: codegraph-linux-amd64
- os: windows-latest
target: x86_64-pc-windows-msvc
artifact_name: codegraph-windows-amd64.exe
- os: macos-latest
target: x86_64-apple-darwin
artifact_name: codegraph-macos-amd64
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install system dependencies (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y build-essential pkg-config libssl-dev protobuf-compiler
- name: Install system dependencies (macOS)
if: matrix.os == 'macos-latest'
run: |
brew install pkg-config protobuf
- name: Install system dependencies (Windows)
if: matrix.os == 'windows-latest'
run: |
choco install protoc
- name: Setup Cargo cache
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-release-${{ hashFiles('**/Cargo.lock') }}
- name: Build release
run: |
cargo build --workspace --release --target ${{ matrix.target }}
- name: Run release tests
run: |
cargo test --workspace --release --target ${{ matrix.target }}
- name: Prepare release artifacts
run: |
mkdir -p release-artifacts
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
cp target/${{ matrix.target }}/release/codegraph-api.exe release-artifacts/${{ matrix.artifact_name }}
else
cp target/${{ matrix.target }}/release/codegraph-api release-artifacts/${{ matrix.artifact_name }}
fi
- name: Upload release artifacts
uses: actions/upload-artifact@v4
with:
name: release-${{ matrix.target }}
path: release-artifacts/
retention-days: 7
# Docker build and push job
docker:
name: Docker Build & Push
runs-on: ubuntu-latest
timeout-minutes: 30
needs: [test-matrix, security-audit]
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }}
format: sarif
output: trivy-results.sarif
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: trivy-results.sarif
# Benchmark job (if benchmarks exist)
benchmark:
name: Performance Benchmarks
runs-on: ubuntu-latest
timeout-minutes: 30
needs: [test-matrix]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential pkg-config libssl-dev protobuf-compiler
- name: Setup Cargo cache
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ubuntu-latest-cargo-bench-${{ hashFiles('**/Cargo.lock') }}
- name: Run benchmarks
run: |
if find . -name "*.rs" -path "*/benches/*" | grep -q .; then
echo "Running benchmarks..."
cargo bench --workspace 2>&1 | tee benchmark-results.txt
else
echo "No benchmarks found, skipping..."
touch benchmark-results.txt
fi
- name: Upload benchmark results
uses: actions/upload-artifact@v4
with:
name: benchmark-results
path: benchmark-results.txt
retention-days: 30
# Summary job
ci-success:
name: CI Success
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [pre-check, security-audit, test-matrix, coverage, advanced-linting]
if: always()
steps:
- name: Check job results
run: |
echo "## CI/CD Pipeline Results" >> $GITHUB_STEP_SUMMARY
echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY
echo "|-----|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Pre-checks | ${{ needs.pre-check.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Security Audit | ${{ needs.security-audit.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Test Matrix | ${{ needs.test-matrix.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Coverage | ${{ needs.coverage.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Advanced Linting | ${{ needs.advanced-linting.result }} |" >> $GITHUB_STEP_SUMMARY
# Check if any required job failed
if [[ "${{ needs.security-audit.result }}" == "failure" ]] ||
[[ "${{ needs.test-matrix.result }}" == "failure" ]]; then
echo "ā CI pipeline failed - check individual job results above"
exit 1
else
echo "ā
CI pipeline completed successfully"
fi
- name: Report status to dev guild
if: always()
run: |
status="${{ job.status }}"
if [[ "$status" == "success" ]]; then
echo "CI/CD pipeline completed successfully for ${{ github.ref }}"
else
echo "CI/CD pipeline failed for ${{ github.ref }}"
fi