Skip to content

web: v1.1.1

web: v1.1.1 #63

Workflow file for this run

name: CI - Continuous Integration
on:
push:
branches:
- production
- develop
- 'feature/**'
paths:
- 'apps/web/**'
- 'apps/api/**'
- 'apps/api-springboot/**'
- 'infra/**'
- 'ansible/**'
- 'terraform/**'
- 'monitoring/**'
- 'docker-compose.yml'
- '.github/workflows/**'
pull_request:
branches:
- production
- develop
paths:
- 'apps/web/**'
- 'apps/api/**'
- 'apps/api-springboot/**'
- 'infra/**'
- 'ansible/**'
- 'terraform/**'
- 'monitoring/**'
- 'docker-compose.yml'
- '.github/workflows/**'
permissions:
contents: read
security-events: write
actions: read
env:
NODE_VERSION: '24.3.0'
JAVA_VERSION: '21'
jobs:
# ============================================
# Detect Changes
# ============================================
changes:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
outputs:
web: ${{ steps.filter.outputs.web }}
api: ${{ steps.filter.outputs.api }}
springboot: ${{ steps.filter.outputs.springboot }}
infra: ${{ steps.filter.outputs.infra }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
web:
- 'apps/web/**'
- '.github/workflows/**'
api:
- 'apps/api/**'
- '.github/workflows/**'
springboot:
- 'apps/api-springboot/**'
- '.github/workflows/**'
infra:
- 'infra/**'
- 'ansible/**'
- 'terraform/**'
- 'monitoring/**'
- 'docker-compose.yml'
- '.github/workflows/**'
# ============================================
# Web (Next.js) CI
# ============================================
web-ci:
name: Web - Build & Test
needs: changes
if: ${{ needs.changes.outputs.web == 'true' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/web
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: |
if npm ci 2>/dev/null; then
echo "✅ npm ci succeeded"
else
echo "⚠️ npm ci failed, falling back to npm install"
npm install
fi
- name: Run linter
run: npm run lint
- name: Type check
run: npx tsc --noEmit
- name: Build application
run: npm run build
env:
NEXT_PUBLIC_API_URL: http://localhost:3001
- name: Run tests
run: npm test -- --passWithNoTests
# ============================================
# API NestJS CI
# ============================================
api-nestjs-ci:
name: API NestJS - Build & Test
needs: changes
if: ${{ needs.changes.outputs.api == 'true' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/api
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: flowly_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: |
if npm ci 2>/dev/null; then
echo "✅ npm ci succeeded"
else
echo "⚠️ npm ci failed, falling back to npm install"
npm install
fi
- name: Run linter
run: npm run lint
- name: Type check
run: npx tsc --noEmit
- name: Build application
run: npm run build
- name: Run tests with coverage
run: npm run test:cov
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/flowly_test
REDIS_HOST: localhost
REDIS_PORT: 6379
JWT_SECRET: test-secret-key
- name: Check coverage threshold
run: |
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
echo "Coverage: $COVERAGE%"
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "❌ Coverage is below 80%"
exit 1
fi
echo "✅ Coverage meets 80% threshold"
- name: Upload coverage reports
uses: codecov/codecov-action@v4
with:
files: ./apps/api/coverage/lcov.info
flags: api-nestjs
fail_ci_if_error: false
# ============================================
# API Spring Boot CI
# ============================================
api-springboot-ci:
name: API Spring Boot - Build & Test
needs: changes
if: ${{ needs.changes.outputs.springboot == 'true' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/api-springboot
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: flowly_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: ${{ env.JAVA_VERSION }}
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Run tests with coverage
run: ./gradlew test jacocoTestReport
env:
SPRING_PROFILES_ACTIVE: ci
JWT_SECRET: your_very_secure_and_long_secret_key_for_jwt_signing_at_least_32_bytes
- name: Check coverage threshold
run: ./gradlew jacocoTestCoverageVerification
continue-on-error: true
- name: Build application
run: ./gradlew assemble
- name: Upload coverage reports
uses: codecov/codecov-action@v4
with:
files: ./apps/api-springboot/build/reports/jacoco/test/jacocoTestReport.xml
flags: api-springboot
fail_ci_if_error: false
# ============================================
# Infrastructure - Terraform & Ansible
# ============================================
infra-ci:
name: Infrastructure - Validate
needs: changes
if: ${{ needs.changes.outputs.infra == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.5.0
- name: Terraform Format Check (dev-with-ansible)
working-directory: terraform/environments/dev-with-ansible
run: terraform fmt -check -recursive
- name: Terraform Init (dev-with-ansible)
working-directory: terraform/environments/dev-with-ansible
run: terraform init -backend=false
- name: Terraform Validate (dev-with-ansible)
working-directory: terraform/environments/dev-with-ansible
run: terraform validate
- name: Setup Python for Ansible
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Ansible and dependencies
run: |
pip install ansible ansible-lint boto3 botocore checkov
- name: Ansible Lint (AWS dev environment)
working-directory: ansible/aws
run: |
ansible-lint playbooks/*.yml || true
- name: Ansible Lint (on-premise environment)
working-directory: ansible/on-premise
run: |
ansible-lint playbooks/*.yml || true
- name: Run Checkov IaC scan
run: |
checkov --directory terraform/ \
--framework terraform \
--output sarif \
--output-file-path checkov-results.sarif \
--soft-fail
continue-on-error: true
- name: Upload Checkov results to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: always()
continue-on-error: true
with:
sarif_file: checkov-results.sarif
category: 'checkov-iac'
# ============================================
# Security Scanning - Trivy
# ============================================
trivy-scan:
name: Security - Trivy Scan
runs-on: ubuntu-latest
needs: [web-ci, api-nestjs-ci, api-springboot-ci]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner (web)
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: 'apps/web'
format: 'sarif'
output: 'trivy-web-results.sarif'
- name: Upload Trivy results (web)
uses: github/codeql-action/upload-sarif@v4
if: always()
continue-on-error: true
with:
sarif_file: 'trivy-web-results.sarif'
category: 'trivy-web'
- name: Run Trivy vulnerability scanner (api-nestjs)
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: 'apps/api'
format: 'sarif'
output: 'trivy-api-nestjs-results.sarif'
- name: Upload Trivy results (api-nestjs)
uses: github/codeql-action/upload-sarif@v4
if: always()
continue-on-error: true
with:
sarif_file: 'trivy-api-nestjs-results.sarif'
category: 'trivy-api-nestjs'
- name: Run Trivy vulnerability scanner (api-springboot)
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: 'apps/api-springboot'
format: 'sarif'
output: 'trivy-api-springboot-results.sarif'
- name: Upload Trivy results (api-springboot)
uses: github/codeql-action/upload-sarif@v4
if: always()
continue-on-error: true
with:
sarif_file: 'trivy-api-springboot-results.sarif'
category: 'trivy-api-springboot'
# ============================================
# Security Scanning - Snyk
# ============================================
snyk-scan:
name: Security - Snyk Scan
runs-on: ubuntu-latest
needs: [web-ci, api-nestjs-ci, api-springboot-ci]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: ${{ env.JAVA_VERSION }}
- name: Run Snyk to check for vulnerabilities (web)
uses: snyk/actions/node@master
continue-on-error: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --sarif-file-output=snyk-web.sarif --file=apps/web/package.json
command: test
- name: Upload Snyk results (web)
uses: github/codeql-action/upload-sarif@v4
if: always()
continue-on-error: true
with:
sarif_file: 'snyk-web.sarif'
category: 'snyk-web'
- name: Run Snyk to check for vulnerabilities (api-nestjs)
uses: snyk/actions/node@master
continue-on-error: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --sarif-file-output=snyk-api-nestjs.sarif --file=apps/api/package.json
command: test
- name: Upload Snyk results (api-nestjs)
uses: github/codeql-action/upload-sarif@v4
if: always()
continue-on-error: true
with:
sarif_file: 'snyk-api-nestjs.sarif'
category: 'snyk-api-nestjs'
- name: Run Snyk to check for vulnerabilities (api-springboot)
uses: snyk/actions/gradle@master
continue-on-error: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --sarif-file-output=snyk-api-springboot.sarif --file=apps/api-springboot/build.gradle
command: test
- name: Upload Snyk results (api-springboot)
uses: github/codeql-action/upload-sarif@v4
if: always()
continue-on-error: true
with:
sarif_file: 'snyk-api-springboot.sarif'
category: 'snyk-api-springboot'
# ============================================
# Security Scanning - Semgrep
# ============================================
semgrep-scan:
name: Security - Semgrep Scan
runs-on: ubuntu-latest
needs: [web-ci, api-nestjs-ci, api-springboot-ci]
container:
image: semgrep/semgrep
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Semgrep scan
run: |
semgrep scan \
--config auto \
--sarif \
--output semgrep-results.sarif \
--verbose
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
continue-on-error: true
- name: Upload Semgrep results to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: always()
continue-on-error: true
with:
sarif_file: semgrep-results.sarif
category: 'semgrep'
# ============================================
# Summary
# ============================================
ci-summary:
name: CI Summary
runs-on: ubuntu-latest
needs: [web-ci, api-nestjs-ci, api-springboot-ci, infra-ci, trivy-scan, snyk-scan, semgrep-scan]
if: always()
steps:
- name: Check CI Results
run: |
echo "🎯 CI Pipeline Results:"
echo "========================"
echo "Web CI: ${{ needs.web-ci.result }}"
echo "API NestJS CI: ${{ needs.api-nestjs-ci.result }}"
echo "API Spring Boot CI: ${{ needs.api-springboot-ci.result }}"
echo "Infrastructure CI: ${{ needs.infra-ci.result }}"
echo "========================"
echo "Security Scans:"
echo " Trivy: ${{ needs.trivy-scan.result }}"
echo " Snyk: ${{ needs.snyk-scan.result }}"
echo " Semgrep: ${{ needs.semgrep-scan.result }}"
echo " Checkov: included in infra-ci"
echo "========================"
if [[ "${{ needs.web-ci.result }}" == "failure" ]] || \
[[ "${{ needs.api-nestjs-ci.result }}" == "failure" ]] || \
[[ "${{ needs.api-springboot-ci.result }}" == "failure" ]] || \
[[ "${{ needs.infra-ci.result }}" == "failure" ]]; then
echo "❌ CI Pipeline Failed"
exit 1
fi
echo "✅ All CI Checks Passed"
echo "ℹ️ Security scans are informational (continue-on-error enabled)"