Files
ShieldAI/.github/workflows/ci.yml
Michael Freno 7b925c89bd Fix 3 Code Review findings on FRE-4574
- P2: Replace wget with curl for ECS health check (Alpine lacks wget)
- P2: Add AWS credentials step to CI terraform-plan job for S3 backend auth
- P3: Remove unused GitHub provider from infra/main.tf

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-10 07:09:39 -04:00

252 lines
7.7 KiB
YAML

name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
NODE_VERSION: "20"
PNPM_VERSION: "9"
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "pnpm"
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run linter
run: pnpm lint
typecheck:
name: Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "pnpm"
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build all packages
run: pnpm build
test:
name: Test Suite
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: shieldai
POSTGRES_USER: shieldai
POSTGRES_PASSWORD: shieldai_dev
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U shieldai"
--health-interval 5s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "pnpm"
- uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run tests with coverage
run: pnpm test:coverage
env:
DATABASE_URL: "postgresql://shieldai:shieldai_dev@localhost:5432/shieldai"
REDIS_URL: "redis://localhost:6379"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./coverage/lcov.info
flags: unittests
name: shieldai-coverage
fail_on_empty: false
docker-build:
name: Docker Build
runs-on: ubuntu-latest
needs: [lint, typecheck, test]
strategy:
fail-fast: false
matrix:
include:
- name: api
context: .
dockerfile: packages/api/Dockerfile
- name: darkwatch
context: .
dockerfile: services/darkwatch/Dockerfile
- name: spamshield
context: .
dockerfile: services/spamshield/Dockerfile
- name: voiceprint
context: .
dockerfile: services/voiceprint/Dockerfile
steps:
- uses: actions/checkout@v4
- name: Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
push: false
tags: shieldai-${{ matrix.name }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
security-scan:
name: Security Scan
runs-on: ubuntu-latest
needs: [lint]
steps:
- uses: actions/checkout@v4
- name: Run pnpm audit
run: pnpm audit --prod
- name: Trivy filesystem scan
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
scan-ref: "."
format: table
exit-code: 1
ignore-unfixed: true
severity: CRITICAL,HIGH
terraform-plan:
name: Terraform Plan
runs-on: ubuntu-latest
needs: [lint]
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Terraform Format
working-directory: infra
run: terraform fmt -check -diff
- name: Terraform Init
working-directory: infra
run: terraform init
- name: Terraform Validate
working-directory: infra
run: terraform validate
- name: Terraform Plan
working-directory: infra
run: terraform plan -var-file=environments/staging/terraform.tfvars.example -no-color
env:
TF_VAR_hibp_api_key: ${{ secrets.HIBP_API_KEY }}
TF_VAR_resend_api_key: ${{ secrets.RESEND_API_KEY }}
load-test:
name: Load Test
runs-on: ubuntu-latest
needs: [lint, typecheck, test, docker-build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
environment: staging
steps:
- uses: actions/checkout@v4
- name: Install k6
run: |
curl -s https://github.com/grafana/k6/releases/download/v0.50.0/k6-linux-amd64.tar.gz -L | tar xz
sudo mv k6 /usr/local/bin/
k6 version
- name: Run combined load tests
run: |
chmod +x scripts/load-test/run-all.sh
./scripts/load-test/run-all.sh
env:
LOAD_TEST_BASE_URL: ${{ secrets.LOAD_TEST_BASE_URL || 'http://localhost:3000' }}
API_TOKEN: ${{ secrets.LOAD_TEST_API_TOKEN || 'test-token' }}
TARGET_RPS: ${{ vars.LOAD_TEST_TARGET_RPS || '500' }}
DURATION: ${{ vars.LOAD_TEST_DURATION || '300s' }}
K6_CLOUD_TOKEN: ${{ secrets.K6_CLOUD_TOKEN || '' }}
K6_CLOUD_PROJECT_ID: ${{ vars.K6_CLOUD_PROJECT_ID || '' }}
- name: Upload load test report
if: always()
uses: actions/upload-artifact@v4
with:
name: load-test-report-${{ github.sha }}
path: scripts/load-test/reports/
retention-days: 30
- name: Check P99 thresholds
if: always()
run: |
if [ -f scripts/load-test/reports/threshold-results.json ]; then
FAILURES=$(jq -r '[.services | to_entries[] | select(.value.exitCode != 0) | .key] | join(", ")' scripts/load-test/reports/threshold-results.json 2>/dev/null || echo "")
if [ -n "$FAILURES" ] && [ "$FAILURES" != "" ]; then
echo "❌ Load test failures: $FAILURES"
exit 1
else
echo "✅ All load tests passed"
fi
else
echo "⚠️ No threshold results file found"
exit 1
fi
- name: Validate auto-scaling
if: always()
run: |
SUMMARY_FILE=$(ls scripts/load-test/reports/*-summary-*.json 2>/dev/null | head -1)
if [ -n "$SUMMARY_FILE" ]; then
MAX_VUS=$(jq -r '.metrics.vus.max // 0' "$SUMMARY_FILE")
TARGET_VUS=20
if [ "$(echo "$MAX_VUS >= $TARGET_VUS" | bc -l)" -eq 1 ]; then
echo "✅ Auto-scaling validated: max VUs ($MAX_VUS) >= target ($TARGET_VUS)"
else
echo "⚠️ Auto-scaling below target: max VUs ($MAX_VUS) < target ($TARGET_VUS)"
fi
else
echo "⚠️ No summary file for auto-scaling validation"
fi