ci(deps): bump actions/checkout from 4 to 6 #366
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main, master, develop] | |
| tags: ['v*'] | |
| pull_request: | |
| branches: [main, master, develop] | |
| env: | |
| GO_VERSION: '1.26' | |
| jobs: | |
| # Job 1: Lint and Format Check | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - name: Run gofmt | |
| run: | | |
| if [ "$(gofmt -l . | wc -l)" -gt 0 ]; then | |
| echo "The following files need formatting:" | |
| gofmt -l . | |
| exit 1 | |
| fi | |
| - name: Run golangci-lint | |
| uses: golangci/golangci-lint-action@v6 | |
| with: | |
| version: v1.64.8 | |
| args: --timeout=5m | |
| continue-on-error: true | |
| - name: go staticcheck (fallback) | |
| if: failure() | |
| run: | | |
| go install honnef.co/go/tools/cmd/staticcheck@v0.6.1 2>/dev/null || true | |
| which staticcheck && staticcheck ./... || echo "staticcheck not available, skipping" | |
| # Job 2: Build Frontend (shared by test, test-race, build jobs) | |
| build-frontend: | |
| name: Build Frontend | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Cache npm modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.npm | |
| key: ${{ runner.os }}-npm-${{ hashFiles('internal/webui/package-lock.json') }} | |
| restore-keys: | | |
| ${{ runner.os }}-npm- | |
| - name: Install frontend dependencies | |
| working-directory: ./internal/webui | |
| run: npm ci --legacy-peer-deps | |
| - name: Lint frontend | |
| working-directory: ./internal/webui | |
| run: npm run lint | |
| continue-on-error: true | |
| # - name: Run frontend tests | |
| # working-directory: ./internal/webui | |
| # run: npm test | |
| - name: Install Playwright browsers | |
| working-directory: ./internal/webui | |
| run: npx playwright install --with-deps chromium | |
| - name: Run E2E tests | |
| working-directory: ./internal/webui | |
| run: npm run test:e2e | |
| - name: Build frontend | |
| working-directory: ./internal/webui | |
| run: npm run build | |
| continue-on-error: true | |
| - name: Upload frontend artifacts | |
| if: success() || failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: frontend-assets | |
| path: | | |
| internal/webui/assets/ | |
| internal/webui/index.html | |
| internal/webui/favicon.svg | |
| retention-days: 1 | |
| # Job 3: Test (multi-OS matrix for cross-platform correctness) | |
| test: | |
| name: Test (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| needs: [build-frontend] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| go-version: ['1.26'] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Download frontend artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: frontend-assets | |
| path: ${{ runner.os == 'Windows' && 'internal/webui/' || '/tmp/frontend-assets/' }} | |
| - name: Move frontend artifacts to webui (Unix fix) | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| rm -rf internal/webui/assets internal/webui/index.html internal/webui/favicon.svg 2>/dev/null || true | |
| cp -r /tmp/frontend-assets/* internal/webui/ | |
| rm -rf /tmp/frontend-assets/ | |
| - name: Setup Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: ${{ matrix.go-version }} | |
| - name: Cache Go modules | |
| if: runner.os != 'macOS' | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go- | |
| - name: Download dependencies | |
| run: go mod download | |
| - name: Run tests (Unix with coverage) | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| if [ "${{ runner.os }}" = "macOS" ]; then | |
| # Skip flaky packages on macOS (timing-sensitive tests) | |
| go test -p 1 -short -v -count=1 -timeout=600s $(go list ./... | grep -v 'internal/health' | grep -v 'internal/proxy/l7') | |
| else | |
| go test -p 1 -short -v -count=1 -coverprofile=coverage.out -timeout=600s ./... | |
| fi | |
| - name: Run tests (Windows) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: go test -p 1 -short -v -count=1 -coverprofile=coverage.out -timeout=600s $(go list ./... | grep -v 'test/e2e') | |
| - name: Generate coverage report | |
| if: runner.os != 'macOS' | |
| shell: bash | |
| run: go tool cover -func=coverage.out | |
| - name: Enforce coverage threshold | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//') | |
| THRESHOLD=85 | |
| echo "Total coverage: ${COVERAGE}%" | |
| echo "Required threshold: ${THRESHOLD}%" | |
| if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) )); then | |
| echo "::error::Coverage ${COVERAGE}% is below threshold ${THRESHOLD}%" | |
| exit 1 | |
| fi | |
| echo "Coverage check passed" | |
| - name: Check per-package coverage | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| THRESHOLD=85 | |
| echo "Checking per-package coverage (threshold: ${THRESHOLD}%)..." | |
| FAIL=0 | |
| go tool cover -func=coverage.out | grep -v total | \ | |
| awk -F'/' '{pkg=""; for(i=1;i<=NF-1;i++){if(i>1)pkg=pkg"/";pkg=pkg$$i}} {print pkg, $$NF}' | \ | |
| awk '{cov[$1]+=$3; cnt[$1]++} END {for(p in cov) {printf "%s %.1f\n", p, cov[p]/cnt[p]}}' | \ | |
| sort | \ | |
| while read pkg pct; do | |
| IPCT=$(echo "$pct" | sed 's/\..*//') | |
| if [ "$IPCT" -lt "$THRESHOLD" ] 2>/dev/null; then | |
| echo "::warning::$pkg ${pct}% is below ${THRESHOLD}%" | |
| fi | |
| done | |
| echo "Per-package coverage check complete" | |
| - name: Upload coverage to Codecov | |
| if: runner.os == 'Linux' && matrix.go-version == env.GO_VERSION | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| file: ./coverage.out | |
| flags: unittests | |
| name: codecov-umbrella | |
| continue-on-error: true | |
| # Job 4: Race Detector Tests (Linux only, requires CGO/GCC) | |
| test-race: | |
| name: Test (Race Detector) | |
| runs-on: ubuntu-latest | |
| needs: [test, build-frontend] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Download frontend artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: frontend-assets | |
| path: ${{ runner.os == 'Windows' && 'internal/webui/' || '/tmp/frontend-assets/' }} | |
| - name: Move frontend artifacts to webui (Unix fix) | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| rm -rf internal/webui/assets internal/webui/index.html internal/webui/favicon.svg 2>/dev/null || true | |
| cp -r /tmp/frontend-assets/* internal/webui/ | |
| rm -rf /tmp/frontend-assets/ | |
| - name: Setup Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - name: Cache Go modules | |
| if: runner.os != 'macOS' | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go- | |
| - name: Download dependencies | |
| run: go mod download | |
| - name: Run tests with race detector | |
| run: go test -p 1 -race -short -count=1 -timeout=600s ./... | |
| # Job 5: Build | |
| build: | |
| name: Build | |
| runs-on: ubuntu-latest | |
| needs: [lint, test, build-frontend] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Download frontend artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: frontend-assets | |
| path: ${{ runner.os == 'Windows' && 'internal/webui/' || '/tmp/frontend-assets/' }} | |
| - name: Move frontend artifacts to webui (Unix fix) | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| rm -rf internal/webui/assets internal/webui/index.html internal/webui/favicon.svg 2>/dev/null || true | |
| cp -r /tmp/frontend-assets/* internal/webui/ | |
| rm -rf /tmp/frontend-assets/ | |
| - name: Setup Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - name: Cache Go modules | |
| if: runner.os != 'macOS' | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go- | |
| - name: Build binary | |
| run: make build | |
| - name: Check binary size | |
| run: | | |
| BINARY="bin/olb" | |
| if [ ! -f "$BINARY" ]; then | |
| echo "ERROR: Binary not found at $BINARY" | |
| exit 1 | |
| fi | |
| SIZE=$(stat --format=%s "$BINARY") | |
| SIZE_MB=$((SIZE / 1024 / 1024)) | |
| echo "Binary size: ${SIZE} bytes (${SIZE_MB} MB)" | |
| MAX_SIZE=$((20 * 1024 * 1024)) | |
| if [ "$SIZE" -gt "$MAX_SIZE" ]; then | |
| echo "ERROR: Binary size ${SIZE_MB}MB exceeds 20MB limit" | |
| exit 1 | |
| fi | |
| echo "PASS: Binary size is within the 20MB limit" | |
| - name: Measure startup time | |
| run: | | |
| BINARY="bin/olb" | |
| echo "Measuring startup time (olb version)..." | |
| # Warm up filesystem cache | |
| "$BINARY" version > /dev/null 2>&1 || true | |
| # Measure 5 runs | |
| TOTAL=0 | |
| for i in 1 2 3 4 5; do | |
| START=$(date +%s%N) | |
| "$BINARY" version > /dev/null 2>&1 || true | |
| END=$(date +%s%N) | |
| ELAPSED=$(( (END - START) / 1000000 )) | |
| echo " Run $i: ${ELAPSED}ms" | |
| TOTAL=$((TOTAL + ELAPSED)) | |
| done | |
| AVG=$((TOTAL / 5)) | |
| echo "Average startup time: ${AVG}ms" | |
| if [ "$AVG" -gt 5000 ]; then | |
| echo "WARNING: Startup time exceeds 5 seconds" | |
| fi | |
| - name: Build all platforms | |
| run: make build-all | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: binaries | |
| path: bin/ | |
| # Job 6: Integration Tests | |
| integration: | |
| name: Integration Tests | |
| runs-on: ubuntu-latest | |
| needs: [build] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Download frontend artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: frontend-assets | |
| path: internal/webui/ | |
| - name: Setup Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - name: Run integration tests | |
| run: go test -v -short -count=1 -p 1 -tags=integration -timeout=300s ./test/... | |
| # Job 7: Benchmark | |
| benchmark: | |
| name: Benchmark | |
| runs-on: ubuntu-latest | |
| needs: [test] | |
| if: github.event_name == 'pull_request' | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - name: Checkout PR branch | |
| uses: actions/checkout@v6 | |
| - name: Setup Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - name: Cache Go modules | |
| if: runner.os != 'macOS' | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go- | |
| - name: Install benchstat | |
| run: go install golang.org/x/perf/cmd/benchstat@v0.0.0-20250306133421-1eb4f625a7d6 | |
| - name: Run baseline benchmarks (base branch) | |
| run: | | |
| git fetch origin ${{ github.base_ref }} | |
| git checkout origin/${{ github.base_ref }} | |
| go test -bench=. -benchmem -count=5 -run=^$ -timeout=600s ./... > baseline.txt 2>&1 || true | |
| - name: Run PR benchmarks | |
| run: | | |
| git checkout ${{ github.event.pull_request.head.sha }} | |
| go test -bench=. -benchmem -count=5 -run=^$ -timeout=600s ./... > pr.txt 2>&1 || true | |
| - name: Compare benchmarks | |
| id: benchstat | |
| run: | | |
| benchstat baseline.txt pr.txt > comparison.md 2>&1 || true | |
| echo "## Benchmark Comparison" > comment.md | |
| echo "" >> comment.md | |
| echo '```' >> comment.md | |
| cat comparison.md >> comment.md | |
| echo '```' >> comment.md | |
| cat comparison.md | |
| - name: Post benchmark comparison | |
| if: success() | |
| continue-on-error: true | |
| run: gh pr comment ${{ github.event.pull_request.number }} --body-file comment.md | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Upload benchmark results | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: benchmark-results | |
| path: | | |
| baseline.txt | |
| pr.txt | |
| comparison.md | |
| retention-days: 30 | |
| # Job 8: Docker Build | |
| docker: | |
| name: Docker Build | |
| runs-on: ubuntu-latest | |
| needs: [lint, test] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup QEMU | |
| uses: docker/setup-qemu-action@v4 | |
| - name: Setup Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Build Docker image | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| push: false | |
| tags: ghcr.io/openloadbalancer/olb:latest | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # Job 9: Security Scan | |
| security: | |
| name: Security Scan | |
| runs-on: ubuntu-latest | |
| needs: [build] | |
| permissions: | |
| security-events: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - name: Run Gosec Security Scanner | |
| uses: securego/gosec@v2.22.3 | |
| with: | |
| args: '-severity high -quiet ./...' | |
| - name: Verify module checksums | |
| run: go mod verify | |
| - name: Run govulncheck | |
| run: | | |
| go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 | |
| govulncheck ./... | |
| - name: Run Nancy (dependency vulnerability scanner) | |
| run: | | |
| go install github.com/sonatypecommunity/nancy@v1.0.106 | |
| go list -json -deps ./... | nancy sleuth | |
| continue-on-error: true | |
| # Job 10: Binary Analysis | |
| binary: | |
| name: Binary Analysis | |
| runs-on: ubuntu-latest | |
| needs: [build] | |
| steps: | |
| - name: Download artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: binaries | |
| path: bin/ | |
| - name: Binary size analysis | |
| run: | | |
| ls -lh bin/ | |
| for f in bin/olb-*; do | |
| SIZE=$(stat -c%s "$f") | |
| echo "$f: $SIZE bytes" | |
| if [ "$SIZE" -gt 20971520 ]; then | |
| echo "ERROR: $f exceeds 20MB limit" | |
| exit 1 | |
| fi | |
| done | |
| - name: Binary verification | |
| run: | | |
| file bin/olb-linux-amd64 | |
| chmod +x bin/olb-linux-amd64 | |
| bin/olb-linux-amd64 version | |
| - name: Check for debug symbols | |
| run: | | |
| if nm bin/olb-linux-amd64 2>/dev/null | head -5 | grep -q "T main"; then | |
| echo "WARNING: Binary contains debug symbols" | |
| else | |
| echo "Debug symbols stripped: OK" | |
| fi | |
| # Job 11: Container Image Security Scan | |
| image-scan: | |
| name: Container Image Scan | |
| runs-on: ubuntu-latest | |
| needs: [docker] | |
| permissions: | |
| security-events: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Build Docker image for scanning | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| push: false | |
| load: true | |
| tags: ghcr.io/openloadbalancer/olb:scan-target | |
| - name: Run Trivy vulnerability scanner | |
| uses: aquasecurity/trivy-action@v0.35.0 | |
| with: | |
| image-ref: ghcr.io/openloadbalancer/olb:scan-target | |
| exit-code: "0" | |
| format: "sarif" | |
| output: "trivy-results.sarif" | |
| severity: "CRITICAL,HIGH" | |
| - name: Upload Trivy scan results to GitHub Security tab | |
| uses: github/codeql-action/upload-sarif@v3 | |
| if: always() | |
| with: | |
| sarif_file: "trivy-results.sarif" | |
| # Job 12: Release (delegated to release.yml for full publishing including GHCR) | |
| # release.yml handles: binary builds, GitHub release, SBOM, Docker publish to GHCR | |
| # This job is intentionally left out of ci.yml to avoid duplicate release creation. |