name: CI Pipeline on: workflow_dispatch: push: paths-ignore: - 'docs/**' - 'README.md' - 'CHANGELOG.md' - '.forgejo/ISSUE_TEMPLATE/**' - '**/*.md' pull_request: paths-ignore: - 'docs/**' - 'README.md' - 'CHANGELOG.md' - '.forgejo/ISSUE_TEMPLATE/**' - '**/*.md' concurrency: group: ${{ gitea.workflow }}-${{ gitea.ref }} cancel-in-progress: true permissions: contents: read security-events: write actions: read packages: write env: GO_VERSION: '1.25.0' ZIG_VERSION: '0.15.2' jobs: test: name: Test (ubuntu-latest on self-hosted) runs-on: self-hosted timeout-minutes: 30 services: redis: image: redis:7 ports: - 6379:6379 options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Go run: | REQUIRED_GO="1.25.0" if command -v go &> /dev/null && go version | grep -q "go${REQUIRED_GO}"; then echo "Go ${REQUIRED_GO} already installed - skipping download" else echo "Installing Go ${REQUIRED_GO}..." curl -sL "https://go.dev/dl/go${REQUIRED_GO}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xzf - export PATH="/usr/local/go/bin:$PATH" echo "/usr/local/go/bin" >> $GITHUB_PATH echo "Go ${REQUIRED_GO} installed" fi go version - name: Set up Zig run: | ZIG_VERSION="${{ env.ZIG_VERSION }}" if command -v zig &> /dev/null && zig version | grep -q "${ZIG_VERSION}"; then echo "Zig ${ZIG_VERSION} already installed - skipping download" else echo "Installing Zig ${ZIG_VERSION}..." ZIG_DIR="/usr/local/zig-${ZIG_VERSION}" if [[ "$OSTYPE" == "linux-gnu"* ]]; then curl -fsSL --retry 3 "https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-linux-${ZIG_VERSION}.tar.xz" -o /tmp/zig.tar.xz sudo mkdir -p "${ZIG_DIR}" sudo tar -C "${ZIG_DIR}" --strip-components=1 -xJf /tmp/zig.tar.xz sudo ln -sf "${ZIG_DIR}/zig" /usr/local/bin/zig elif [[ "$OSTYPE" == "darwin"* ]]; then curl -fsSL --retry 3 "https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-macos-${ZIG_VERSION}.tar.xz" -o /tmp/zig.tar.xz sudo mkdir -p "${ZIG_DIR}" sudo tar -C "${ZIG_DIR}" --strip-components=1 -xJf /tmp/zig.tar.xz sudo ln -sf "${ZIG_DIR}/zig" /usr/local/bin/zig fi rm -f /tmp/zig.tar.xz echo "Zig ${ZIG_VERSION} installed" fi zig version - name: Install dependencies run: | go mod download sudo apt-get update sudo apt-get install -y podman redis-tools build-essential autoconf automake libtool pkg-config - name: Verify dependencies run: go mod verify - name: Run tests run: make test - name: Test internal/queue package run: go test -v -race -coverprofile=queue-coverage.out ./internal/queue/... - name: Run comprehensive tests run: make test - name: Run linters run: make lint - name: Security lint checks run: | echo "=== Security Lint Checks ===" echo "Checking for unsafe os.WriteFile usage..." if grep -rn "os\.WriteFile" internal/ --include="*.go" | grep -v "_test.go" | grep -v "// fsync-exempt"; then echo "ERROR: Found os.WriteFile calls. Use fileutil.WriteFileSafe() instead." echo "Mark exemptions with '// fsync-exempt' comment" exit 1 fi echo "✓ No unsafe os.WriteFile calls found" echo "Checking for O_NOFOLLOW in sensitive paths..." if grep -rn "os\.OpenFile.*O_CREATE" internal/queue/ internal/crypto/ internal/experiment/ --include="*.go" | grep -v "OpenFileNoFollow" | grep -v "_test.go"; then echo "WARNING: File open in sensitive dir may need O_NOFOLLOW" fi echo "✓ O_NOFOLLOW check complete" - name: Generate coverage report run: make test-coverage dev-smoke: name: Dev Compose Smoke Test runs-on: self-hosted needs: test timeout-minutes: 20 steps: - name: Checkout code uses: actions/checkout@v4 - name: Run dev smoke test run: make dev-smoke security-scan: name: Security Scan runs-on: self-hosted needs: test timeout-minutes: 20 steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Go run: | REQUIRED_GO="1.25.0" if command -v go &> /dev/null && go version | grep -q "go${REQUIRED_GO}"; then echo "Go ${REQUIRED_GO} already installed" else echo "Installing Go ${REQUIRED_GO}..." curl -sL "https://go.dev/dl/go${REQUIRED_GO}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xzf - export PATH="/usr/local/go/bin:$PATH" echo "/usr/local/go/bin" >> $GITHUB_PATH echo "Go ${REQUIRED_GO} installed" fi go version - name: Install security scanners run: | # Install gosec curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sudo sh -s -- -b /usr/local/bin latest # Install nancy curl -sfL https://raw.githubusercontent.com/sonatype-nexus-community/nancy/master/install.sh | sudo sh -s -- -b /usr/local/bin latest # Install trivy curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin latest - name: Go source security scan (gosec) run: | echo "Running gosec security scanner..." gosec -fmt sarif -out gosec-results.sarif ./... || { echo "gosec found issues - check gosec-results.sarif" exit 1 } continue-on-error: false - name: Dependency audit (nancy) run: | echo "Running nancy dependency audit..." go list -json -deps ./... | nancy sleuth --output sarif > nancy-results.sarif || { echo "nancy found vulnerable dependencies" cat nancy-results.sarif exit 1 } continue-on-error: false - name: Upload security scan results uses: actions/upload-artifact@v4 if: always() with: name: security-scan-results path: | gosec-results.sarif nancy-results.sarif retention-days: 30 test-scripts: name: Test Scripts runs-on: self-hosted needs: test timeout-minutes: 15 steps: - name: Checkout code uses: actions/checkout@v4 - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y podman redis-tools bats - name: Test scripts run: | chmod +x scripts/*.sh || true chmod +x scripts/maintenance/*.sh || true ./scripts/verify_release.sh --help ./scripts/manage-artifacts.sh help ./scripts/track_performance.sh --help ./scripts/smoke-test.sh --help test-native: name: Test Native Libraries runs-on: self-hosted needs: native-build-matrix timeout-minutes: 30 services: redis: image: redis:7-alpine ports: - 6379:6379 options: >- --health-cmd "redis-cli ping" --health-interval 5s --health-timeout 3s --health-retries 3 steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - name: Install cmake and build tools run: | echo "Installing cmake and build dependencies..." if [[ "$OSTYPE" == "linux-gnu"* ]]; then if command -v apt-get &> /dev/null; then sudo apt-get update sudo apt-get install -y cmake zlib1g-dev build-essential elif command -v yum &> /dev/null; then sudo yum install -y cmake zlib-devel gcc-c++ fi elif [[ "$OSTYPE" == "darwin"* ]]; then brew install cmake zlib fi which cmake - name: Setup Go run: | REQUIRED_GO="1.25.0" if command -v go &> /dev/null && go version | grep -q "go${REQUIRED_GO}"; then echo "Go ${REQUIRED_GO} already installed - skipping download" else echo "Installing Go ${REQUIRED_GO}..." if [[ "$OSTYPE" == "linux-gnu"* ]]; then curl -sL "https://go.dev/dl/go${REQUIRED_GO}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xzf - elif [[ "$OSTYPE" == "darwin"* ]]; then curl -sL "https://go.dev/dl/go${REQUIRED_GO}.darwin-amd64.tar.gz" | sudo tar -C /usr/local -xzf - fi export PATH="/usr/local/go/bin:$PATH" echo "/usr/local/go/bin" >> $GITHUB_PATH echo "Go ${REQUIRED_GO} installed" fi go version - name: Build Native Libraries run: | echo "Building native C++ libraries..." make native-build 2>&1 || { echo "" echo "Native build failed!" echo "" echo "Common causes:" echo " 1. Missing cmake: Install with 'apt-get install cmake'" echo " 2. Missing C++ compiler: Install with 'apt-get install build-essential'" echo " 3. Missing zlib: Install with 'apt-get install zlib1g-dev'" echo " 4. CMakeLists.txt not found: Ensure native/CMakeLists.txt exists" echo "" exit 1 } echo "Native libraries built successfully" - name: Test with Native Libraries run: | echo "Running tests WITH native libraries enabled..." CGO_ENABLED=1 go test -tags native_libs -v ./tests/... || true - name: Native Smoke Test run: | echo "Running native libraries smoke test..." CGO_ENABLED=1 go test -tags native_libs ./tests/benchmarks/... -run TestNative || true - name: Test Fallback (Go only) run: | echo "Running tests WITHOUT native libraries (Go fallback)..." go test -v ./tests/... || true - name: Run Benchmarks run: | echo "Running performance benchmarks..." echo "=== Go Implementation ===" go test -bench=. ./tests/benchmarks/ -benchmem || true echo "" echo "=== Native Implementation ===" CGO_ENABLED=1 go test -tags native_libs -bench=. ./tests/benchmarks/ -benchmem || true native-build-matrix: name: Native Library Build Matrix runs-on: self-hosted needs: test timeout-minutes: 30 strategy: matrix: build_config: - name: "native" tags: "native_libs" cgo_enabled: "1" build_native: "true" - name: "cgo-only" tags: "" cgo_enabled: "1" build_native: "false" - name: "no-cgo" tags: "" cgo_enabled: "0" build_native: "false" fail-fast: false services: redis: image: redis:7-alpine ports: - 6379:6379 options: >- --health-cmd "redis-cli ping" --health-interval 5s --health-timeout 3s --health-retries 3 steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 1 - name: Install cmake and build tools if: matrix.build_config.build_native == 'true' run: | echo "Installing cmake and build dependencies..." if [[ "$OSTYPE" == "linux-gnu"* ]]; then if command -v apt-get &> /dev/null; then sudo apt-get update sudo apt-get install -y cmake zlib1g-dev build-essential elif command -v yum &> /dev/null; then sudo yum install -y cmake zlib-devel gcc-c++ fi elif [[ "$OSTYPE" == "darwin"* ]]; then brew install cmake zlib fi which cmake - name: Setup Go run: | REQUIRED_GO="1.25.0" if command -v go &> /dev/null && go version | grep -q "go${REQUIRED_GO}"; then echo "Go ${REQUIRED_GO} already installed" else echo "Installing Go ${REQUIRED_GO}..." curl -sL "https://go.dev/dl/go${REQUIRED_GO}.linux-amd64.tar.gz" | sudo tar -C /usr/local -xzf - export PATH="/usr/local/go/bin:$PATH" echo "/usr/local/go/bin" >> $GITHUB_PATH echo "Go ${REQUIRED_GO} installed" fi go version - name: Build Native Libraries if: matrix.build_config.build_native == 'true' run: | echo "Building native C++ libraries..." make native-build 2>&1 || { echo "" echo "Native build failed!" echo "" echo "Common causes:" echo " 1. Missing cmake: Install with 'apt-get install cmake'" echo " 2. Missing C++ compiler: Install with 'apt-get install build-essential'" echo " 3. Missing zlib: Install with 'apt-get install zlib1g-dev'" echo " 4. CMakeLists.txt not found: Ensure native/CMakeLists.txt exists" echo "" exit 1 } echo "Native libraries built successfully" - name: Run tests - ${{ matrix.build_config.name }} run: | echo "=== Testing ${{ matrix.build_config.name }} build (CGO_ENABLED=${{ matrix.build_config.cgo_enabled }}, tags=${{ matrix.build_config.tags }}) ===" CGO_ENABLED=${{ matrix.build_config.cgo_enabled }} go test -tags "${{ matrix.build_config.tags }}" -v ./tests/unit/... || true - name: Run plugin quota tests run: | echo "=== Running Plugin GPU Quota tests ===" go test -v ./tests/unit/scheduler/... -run TestPluginQuota - name: Run service templates tests run: | echo "=== Running Service Templates tests ===" go test -v ./tests/unit/scheduler/... -run TestServiceTemplate - name: Run scheduler tests run: | echo "=== Running Scheduler tests ===" go test -v ./tests/unit/scheduler/... -run TestScheduler - name: Run vLLM plugin tests run: | echo "=== Running vLLM Plugin tests ===" go test -v ./tests/unit/worker/plugins/... -run TestVLLM - name: Run audit tests run: | echo "=== Running Audit Logging tests ===" go test -v ./tests/unit/security/... -run TestAudit go test -v ./tests/integration/audit/... build-trigger: name: Trigger Build Workflow runs-on: self-hosted needs: [test, security-scan, native-build-matrix, dev-smoke, test-scripts] if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main' timeout-minutes: 5 steps: - name: Checkout code uses: actions/checkout@v4 - name: Trigger build workflow run: | echo "All CI checks passed. Build workflow will be triggered." echo "SHA: ${{ gitea.sha }}" echo "Ref: ${{ gitea.ref }}" echo "Repository: ${{ gitea.repository }}"