CI #295
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] | |
| pull_request: | |
| branches: [main, dev] | |
| schedule: | |
| - cron: '0 0 * * 0' # Weekly scan on Sunday at 00:00 UTC | |
| jobs: | |
| contract: | |
| name: Rust / Soroban contract | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust + wasm32 target | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: wasm32-unknown-unknown | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| contracts/target | |
| key: cargo-${{ hashFiles('contracts/Cargo.lock') }} | |
| - name: Build WASM | |
| working-directory: contracts | |
| run: make build | |
| - name: Run contract tests | |
| working-directory: contracts | |
| run: cargo test | |
| - name: Run contract tests with coverage | |
| working-directory: contracts | |
| run: | | |
| cargo install cargo-tarpaulin | |
| make test-coverage | |
| - name: Upload Contract Coverage to Codecov | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| files: contracts/tarpaulin-report.xml | |
| flags: contracts | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| - name: Install cargo-audit | |
| run: cargo install cargo-audit | |
| - name: Dependency audit | |
| working-directory: contracts | |
| run: cargo audit | tee audit-report.txt | |
| - name: Upload audit report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: contract-audit-report | |
| path: contracts/audit-report.txt | |
| backend: | |
| name: Node.js backend | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 18 | |
| cache: npm | |
| cache-dependency-path: backend/package-lock.json | |
| - name: Install dependencies | |
| working-directory: backend | |
| run: npm ci | |
| - name: Lint | |
| working-directory: backend | |
| run: npm run lint | |
| - name: Run tests | |
| working-directory: backend | |
| run: npm test | |
| - name: Run tests with coverage | |
| working-directory: backend | |
| run: npm test | |
| - name: Upload Backend Coverage to Codecov | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| files: backend/coverage/lcov.info | |
| flags: backend | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| - name: Dependency audit | |
| working-directory: backend | |
| run: npm audit --audit-level=high | tee audit-report.txt | |
| - name: Upload audit report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: backend-audit-report | |
| path: backend/audit-report.txt | |
| frontend: | |
| name: Node.js frontend | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 18 | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Install dependencies | |
| working-directory: frontend | |
| run: npm ci | |
| - name: Lint | |
| working-directory: frontend | |
| run: npm run lint | |
| - name: Run tests | |
| working-directory: frontend | |
| run: npm test | |
| - name: Run tests with coverage | |
| working-directory: frontend | |
| run: npm run test:coverage | |
| - name: Upload Frontend Coverage to Codecov | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| files: frontend/coverage/lcov.info | |
| flags: frontend | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| - name: Dependency audit | |
| working-directory: frontend | |
| run: npm audit --audit-level=high | tee audit-report.txt | |
| - name: Upload audit report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: frontend-audit-report | |
| path: frontend/audit-report.txt | |
| python: | |
| name: Python analytics service | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| cache-dependency-path: python-service/requirements.txt | |
| - name: Install dependencies | |
| working-directory: python-service | |
| run: pip install -r requirements.txt | |
| - name: Lint | |
| working-directory: python-service | |
| run: flake8 . | |
| - name: Run tests | |
| working-directory: python-service | |
| run: pytest | |
| - name: Run tests with coverage | |
| working-directory: python-service | |
| run: pytest --cov --cov-report=xml | |
| - name: Upload Python Coverage to Codecov | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| files: python-service/coverage.xml | |
| flags: python | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| - name: Install pip-audit | |
| run: pip install pip-audit | |
| - name: Dependency audit | |
| working-directory: python-service | |
| run: pip-audit -r requirements.txt | tee audit-report.txt | |
| - name: Upload audit report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: python-audit-report | |
| path: python-service/audit-report.txt | |
| verify-perf: | |
| name: k6 performance test — verify endpoint | |
| runs-on: ubuntu-latest | |
| needs: backend | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 18 | |
| cache: npm | |
| cache-dependency-path: backend/package-lock.json | |
| - name: Install backend dependencies | |
| working-directory: backend | |
| run: npm ci | |
| - name: Start backend | |
| working-directory: backend | |
| run: node src/app.js & | |
| env: | |
| NODE_ENV: test | |
| PORT: 4000 | |
| # Minimal env so config validation passes | |
| STELLAR_NETWORK: testnet | |
| HORIZON_URL: https://horizon-testnet.stellar.org | |
| SOROBAN_RPC_URL: https://soroban-testnet.stellar.org | |
| STELLAR_NETWORK_PASSPHRASE: Test SDF Network ; September 2015 | |
| VACCINATIONS_CONTRACT_ID: CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM | |
| ADMIN_SECRET_KEY: SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | |
| ADMIN_PUBLIC_KEY: GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF | |
| SEP10_SERVER_KEY: SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB | |
| ISSUER_SECRET_KEY: SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC | |
| JWT_SECRET: ci-test-secret | |
| - name: Wait for backend | |
| run: | | |
| for i in $(seq 1 30); do | |
| curl -sf http://localhost:4000/health && exit 0 | |
| sleep 2 | |
| done | |
| echo "Backend did not start" && exit 1 | |
| - name: Install k6 | |
| run: | | |
| sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \ | |
| --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 | |
| echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \ | |
| | sudo tee /etc/apt/sources.list.d/k6.list | |
| sudo apt-get update -qq && sudo apt-get install -y k6 | |
| - name: Run k6 load test | |
| working-directory: backend | |
| run: | | |
| k6 run \ | |
| -e BASE_URL=http://localhost:4000 \ | |
| --out json=load-test-results.json \ | |
| load-test.js | |
| - name: Upload load test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: verify-perf-results-${{ github.run_id }} | |
| path: | | |
| backend/load-test-results.json | |
| backend/load-test-summary.json | |
| retention-days: 90 | |
| # Gate job: all test jobs must pass before a PR can be merged. | |
| # Configure this job name ("All tests passed") as a required status check | |
| # in Settings → Branches → Branch protection rules for `main`. | |
| all-tests: | |
| name: All tests passed | |
| runs-on: ubuntu-latest | |
| needs: [contract, backend, frontend, python, verify-perf] | |
| if: always() | |
| steps: | |
| - name: Check all test jobs succeeded | |
| run: | | |
| if [[ "${{ needs.contract.result }}" != "success" || \ | |
| "${{ needs.backend.result }}" != "success" || \ | |
| "${{ needs.frontend.result }}" != "success" || \ | |
| "${{ needs.python.result }}" != "success" || \ | |
| "${{ needs.verify-perf.result }}" != "success" ]]; then | |
| echo "One or more test jobs failed." | |
| exit 1 | |
| fi | |
| echo "All test jobs passed." | |
| docker: | |
| name: Docker build validation | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build backend image | |
| run: docker build ./backend -t vaccichain-backend:ci | |
| - name: Build frontend image | |
| run: docker build ./frontend -t vaccichain-frontend:ci | |
| - name: Build python-service image | |
| run: docker build ./python-service -t vaccichain-python:ci | |
| container-scan: | |
| name: Container vulnerability scanning | |
| runs-on: ubuntu-latest | |
| needs: docker | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build backend image | |
| run: docker build ./backend -t vaccichain-backend:ci | |
| - name: Build frontend image | |
| run: docker build ./frontend -t vaccichain-frontend:ci | |
| - name: Build python-service image | |
| run: docker build ./python-service -t vaccichain-python:ci | |
| - name: Run Trivy scan on backend | |
| id: scan-backend | |
| uses: aquasecurity/trivy-action@v1 | |
| with: | |
| image-ref: vaccichain-backend:ci | |
| format: sarif | |
| output: backend-trivy.sarif | |
| severity: HIGH,CRITICAL | |
| ignorefile: .trivyignore | |
| exit-code: 1 | |
| continue-on-error: true | |
| - name: Run Trivy scan on frontend | |
| id: scan-frontend | |
| uses: aquasecurity/trivy-action@v1 | |
| with: | |
| image-ref: vaccichain-frontend:ci | |
| format: sarif | |
| output: frontend-trivy.sarif | |
| severity: HIGH,CRITICAL | |
| ignorefile: .trivyignore | |
| exit-code: 1 | |
| continue-on-error: true | |
| - name: Run Trivy scan on python-service | |
| id: scan-python | |
| uses: aquasecurity/trivy-action@v1 | |
| with: | |
| image-ref: vaccichain-python:ci | |
| format: sarif | |
| output: python-trivy.sarif | |
| severity: HIGH,CRITICAL | |
| ignorefile: .trivyignore | |
| exit-code: 1 | |
| continue-on-error: true | |
| - name: Upload Trivy results to GitHub Security | |
| if: always() | |
| uses: github/codeql-action/upload-sarif@v3 | |
| with: | |
| sarif_file: | | |
| backend-trivy.sarif | |
| frontend-trivy.sarif | |
| python-trivy.sarif | |
| - name: Upload scan artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: trivy-scan-results | |
| path: | | |
| backend-trivy.sarif | |
| frontend-trivy.sarif | |
| python-trivy.sarif | |
| retention-days: 30 | |
| - name: Fail on HIGH/CRITICAL vulnerabilities | |
| if: always() | |
| run: | | |
| if [[ "${{ steps.scan-backend.outcome }}" == "failure" || "${{ steps.scan-frontend.outcome }}" == "failure" || "${{ steps.scan-python.outcome }}" == "failure" ]]; then | |
| echo "One or more container images contain HIGH or CRITICAL vulnerabilities." | |
| exit 1 | |
| fi | |
| container-scan-scheduled: | |
| name: Weekly container vulnerability scan | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'schedule' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build backend image | |
| run: docker build ./backend -t vaccichain-backend:prod | |
| - name: Build frontend image | |
| run: docker build ./frontend -t vaccichain-frontend:prod | |
| - name: Build python-service image | |
| run: docker build ./python-service -t vaccichain-python:prod | |
| - name: Run Trivy scan on backend | |
| id: scan-scheduled-backend | |
| uses: aquasecurity/trivy-action@v1 | |
| with: | |
| image-ref: vaccichain-backend:prod | |
| format: table | |
| output: backend-scan.txt | |
| ignorefile: .trivyignore | |
| severity: HIGH,CRITICAL | |
| exit-code: 1 | |
| continue-on-error: true | |
| - name: Run Trivy scan on frontend | |
| id: scan-scheduled-frontend | |
| uses: aquasecurity/trivy-action@v1 | |
| with: | |
| image-ref: vaccichain-frontend:prod | |
| format: table | |
| output: frontend-scan.txt | |
| ignorefile: .trivyignore | |
| severity: HIGH,CRITICAL | |
| exit-code: 1 | |
| continue-on-error: true | |
| - name: Run Trivy scan on python-service | |
| id: scan-scheduled-python | |
| uses: aquasecurity/trivy-action@v1 | |
| with: | |
| image-ref: vaccichain-python:prod | |
| format: table | |
| output: python-scan.txt | |
| ignorefile: .trivyignore | |
| severity: HIGH,CRITICAL | |
| exit-code: 1 | |
| continue-on-error: true | |
| - name: Upload scan results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: weekly-trivy-scans | |
| path: | | |
| backend-scan.txt | |
| frontend-scan.txt | |
| python-scan.txt | |
| retention-days: 90 | |
| - name: Fail on HIGH/CRITICAL vulnerabilities | |
| if: always() | |
| run: | | |
| if [[ "${{ steps.scan-scheduled-backend.outcome }}" == "failure" || "${{ steps.scan-scheduled-frontend.outcome }}" == "failure" || "${{ steps.scan-scheduled-python.outcome }}" == "failure" ]]; then | |
| echo "One or more scheduled container images contain HIGH or CRITICAL vulnerabilities." | |
| exit 1 | |
| fi |