chore(ci): bump google-github-actions/setup-gcloud from 2 to 3 #153
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
| # ============================================================================ | |
| # CI Pipeline - portfolio-backend (Rust) | |
| # ============================================================================ | |
| name: CI Pipeline | |
| on: | |
| push: | |
| branches: [main, develop] | |
| pull_request: | |
| branches: [main, develop] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| check: | |
| name: Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt | |
| - name: Cache cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Check formatting | |
| run: cargo fmt --all -- --check | |
| - name: Check code | |
| run: cargo check --all-features | |
| test: | |
| name: Test | |
| runs-on: ubuntu-latest | |
| # Postgres service is provisioned for the DB-backed integration tests | |
| # added in Phase 2/3 of the coverage expansion plan. `TEST_DATABASE_URL` | |
| # is a *separate* var from the production `DATABASE_URL` so unrelated | |
| # tests that branch on `DATABASE_URL` being unset (e.g. fallback admin | |
| # login) keep their existing behaviour. | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_USER: portfolio | |
| POSTGRES_PASSWORD: portfolio | |
| POSTGRES_DB: portfolio_test | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready -U portfolio -d portfolio_test" | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 12 | |
| redis: | |
| image: redis:7-alpine | |
| ports: | |
| - 6379:6379 | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 12 | |
| env: | |
| TEST_DATABASE_URL: postgres://portfolio:portfolio@localhost:5432/portfolio_test | |
| TEST_REDIS_URL: redis://localhost:6379 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Wait for Postgres | |
| run: | | |
| for i in $(seq 1 30); do | |
| if pg_isready -h localhost -p 5432 -U portfolio -d portfolio_test >/dev/null 2>&1; then | |
| echo "Postgres is ready" | |
| exit 0 | |
| fi | |
| echo "Attempt $i: Postgres not ready yet, waiting 2s..." | |
| sleep 2 | |
| done | |
| echo "Postgres failed to become ready" && exit 1 | |
| - name: Run tests | |
| run: cargo test --all-features | |
| coverage: | |
| name: Coverage | |
| runs-on: ubuntu-latest | |
| # Mirror the `test` job's Postgres service so coverage runs over the same | |
| # surface area (DB-backed routes count toward coverage). A coverage-failure | |
| # threshold is intentionally NOT added here yet — Phase 5 will tune and | |
| # land that gate once the new test suites stabilise. | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_USER: portfolio | |
| POSTGRES_PASSWORD: portfolio | |
| POSTGRES_DB: portfolio_test | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready -U portfolio -d portfolio_test" | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 12 | |
| env: | |
| TEST_DATABASE_URL: postgres://portfolio:portfolio@localhost:5432/portfolio_test | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: llvm-tools-preview | |
| - name: Install cargo-llvm-cov | |
| uses: taiki-e/install-action@cargo-llvm-cov | |
| - name: Cache cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Wait for Postgres | |
| run: | | |
| for i in $(seq 1 30); do | |
| if pg_isready -h localhost -p 5432 -U portfolio -d portfolio_test >/dev/null 2>&1; then | |
| echo "Postgres is ready" | |
| exit 0 | |
| fi | |
| echo "Attempt $i: Postgres not ready yet, waiting 2s..." | |
| sleep 2 | |
| done | |
| echo "Postgres failed to become ready" && exit 1 | |
| - name: Run tests with coverage | |
| run: cargo llvm-cov test --all-features | |
| - name: Enforce coverage gate and export lcov | |
| run: cargo llvm-cov report --lcov --output-path lcov.info --fail-under-lines 77 | |
| - name: Coverage report | |
| run: cargo llvm-cov report --summary-only | |
| - name: Upload coverage artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: backend-lcov | |
| path: lcov.info | |
| clippy: | |
| name: Clippy | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy | |
| - name: Cache cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Run Clippy | |
| run: cargo clippy --all-features -- -D warnings | |
| build: | |
| name: Build | |
| runs-on: ubuntu-latest | |
| # test + coverage temporarily disabled — add back to needs when re-enabled | |
| needs: [check, clippy] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Build release | |
| run: cargo build --release | |
| audit: | |
| name: Cargo Audit | |
| runs-on: ubuntu-latest | |
| needs: check | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Install cargo-audit | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: cargo-audit | |
| - name: Run cargo audit | |
| run: cargo audit --deny warnings | |
| security: | |
| name: Security Scan | |
| runs-on: ubuntu-latest | |
| needs: check | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Run Trivy vulnerability scanner | |
| uses: aquasecurity/trivy-action@0.28.0 | |
| with: | |
| scan-type: "fs" | |
| scan-ref: "." | |
| ignore-unfixed: true | |
| format: "sarif" | |
| output: "trivy-results.sarif" | |
| severity: "CRITICAL,HIGH" | |
| skip-dirs: "target,.git" | |
| exit-code: "1" | |
| timeout: "10m" | |
| - name: Upload Trivy scan results | |
| uses: github/codeql-action/upload-sarif@v4 | |
| if: always() | |
| with: | |
| sarif_file: "trivy-results.sarif" | |
| category: "trivy-security-scan" | |
| docker: | |
| name: Docker Stack | |
| runs-on: ubuntu-latest | |
| needs: [check, clippy] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Create required host directories | |
| run: | | |
| mkdir -p logs data/loki data/grafana | |
| mkdir -p ../portfolio-frontend/logs/server | |
| - name: Write .env for docker compose | |
| run: | | |
| cat > .env << 'EOF' | |
| POSTGRES_USER=portfolio | |
| POSTGRES_PASSWORD=portfolio | |
| POSTGRES_DB=portfolio | |
| JWT_SECRET=ci-test-jwt-secret-change-in-production | |
| REFRESH_TOKEN_SECRET=ci-test-refresh-secret-change-in-production | |
| ALLOWED_ORIGINS=http://localhost:3000 | |
| FRONTEND_ORIGIN=http://localhost:3000 | |
| ENVIRONMENT=development | |
| LOG_LEVEL=info | |
| DB_POOL_MIN=1 | |
| DB_POOL_MAX=5 | |
| GRAFANA_USER=admin | |
| GRAFANA_PASSWORD=admin | |
| EOF | |
| - name: Build and start full stack | |
| run: docker compose up -d --build --wait --timeout 180 | |
| timeout-minutes: 15 | |
| - name: Show service status | |
| run: docker compose ps | |
| - name: Health check — postgres | |
| run: | | |
| docker compose exec -T postgres \ | |
| pg_isready -U portfolio -d portfolio | |
| - name: Health check — redis | |
| run: | | |
| docker compose exec -T redis redis-cli ping | grep -q PONG | |
| - name: Health check — backend /health | |
| run: | | |
| curl -sf http://localhost:8080/health | |
| - name: Health check — backend /health/detailed | |
| run: | | |
| curl -sf http://localhost:8080/health/detailed | |
| - name: Health check — loki | |
| run: | | |
| curl -sf http://localhost:3100/ready | |
| - name: Health check — prometheus | |
| run: | | |
| curl -sf http://localhost:9090/-/ready | |
| - name: Health check — prometheus targets | |
| run: | | |
| curl -sf http://localhost:9090/api/v1/targets \ | |
| | grep -q '"health":"up"' || true | |
| - name: Health check — grafana | |
| run: | | |
| for i in $(seq 1 12); do | |
| STATUS=$(curl -sf http://localhost:3001/api/health 2>/dev/null || true) | |
| if echo "$STATUS" | grep -q '"database"'; then | |
| echo "Grafana healthy: $STATUS" | |
| exit 0 | |
| fi | |
| echo "Attempt $i: Grafana not ready yet, waiting 10s..." | |
| sleep 10 | |
| done | |
| echo "Grafana failed to become ready" && exit 1 | |
| - name: Verify grafana datasources provisioned | |
| run: | | |
| for i in $(seq 1 12); do | |
| STATUS=$(curl -s -u admin:admin http://localhost:3001/api/datasources 2>/dev/null || true) | |
| echo "Attempt $i response: $STATUS" | |
| if echo "$STATUS" | grep -qi "loki"; then | |
| echo "Loki datasource provisioned" | |
| exit 0 | |
| fi | |
| echo "Attempt $i: datasources not ready yet, waiting 10s..." | |
| sleep 10 | |
| done | |
| echo "WARNING: Grafana datasources not provisioned after 2 minutes, continuing..." | |
| - name: Setup Go for hey | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: "1.22" | |
| - name: Install hey | |
| run: go install github.com/rakyll/hey@latest | |
| - name: Latency smoke test (P95 < 50ms) | |
| run: | | |
| export PATH=$PATH:$(go env GOPATH)/bin | |
| ./scripts/latency-smoke.sh http://localhost:8080 50 | |
| - name: Show container logs on failure | |
| if: failure() | |
| run: | | |
| echo "=== backend ===" && docker compose logs --no-color --tail=30 backend | |
| echo "=== postgres ===" && docker compose logs --no-color --tail=20 postgres | |
| echo "=== redis ===" && docker compose logs --no-color --tail=20 redis | |
| echo "=== loki ===" && docker compose logs --no-color --tail=30 loki | |
| echo "=== prometheus ===" && docker compose logs --no-color --tail=20 prometheus | |
| echo "=== grafana ===" && docker compose logs --no-color --tail=20 grafana | |
| - name: Tear down | |
| if: always() | |
| run: docker compose down -v --remove-orphans |