Skip to content

chore(ci): bump google-github-actions/setup-gcloud from 2 to 3 #153

chore(ci): bump google-github-actions/setup-gcloud from 2 to 3

chore(ci): bump google-github-actions/setup-gcloud from 2 to 3 #153

Workflow file for this run

# ============================================================================
# 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