fix: apply cargo fmt formatting fixes #10
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
| # ============================================================================= | |
| # Container CI - World-class container testing before merge | |
| # ============================================================================= | |
| name: Container CI | |
| on: | |
| push: | |
| branches: [main, develop] | |
| paths: | |
| - 'apps/*/Dockerfile' | |
| - 'apps/**/*.rs' | |
| - 'apps/**/*.go' | |
| - 'apps/**/*.ex' | |
| - 'apps/**/*.exs' | |
| - 'apps/web/**' | |
| - 'packages/**' | |
| - 'docker-compose.yml' | |
| - '.github/workflows/container-ci.yml' | |
| pull_request: | |
| branches: [main, develop] | |
| paths: | |
| - 'apps/*/Dockerfile' | |
| - 'apps/**/*.rs' | |
| - 'apps/**/*.go' | |
| - 'apps/**/*.ex' | |
| - 'apps/**/*.exs' | |
| - 'apps/web/**' | |
| - 'packages/**' | |
| - 'docker-compose.yml' | |
| - '.github/workflows/container-ci.yml' | |
| workflow_dispatch: | |
| inputs: | |
| containers: | |
| description: 'Containers to test (comma-separated, or "all")' | |
| required: false | |
| default: 'all' | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_PREFIX: ${{ github.repository_owner }}/chronos | |
| jobs: | |
| # =========================================================================== | |
| # Detect which containers changed | |
| # =========================================================================== | |
| changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| core: ${{ steps.filter.outputs.core }} | |
| query-service: ${{ steps.filter.outputs.query-service }} | |
| mcp-server: ${{ steps.filter.outputs.mcp-server }} | |
| control-plane: ${{ steps.filter.outputs.control-plane }} | |
| web: ${{ steps.filter.outputs.web }} | |
| matrix: ${{ steps.set-matrix.outputs.matrix }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dorny/paths-filter@v3 | |
| id: filter | |
| with: | |
| filters: | | |
| core: | |
| - 'apps/core/**' | |
| query-service: | |
| - 'apps/query-service/**' | |
| mcp-server: | |
| - 'apps/mcp-server-elixir/**' | |
| control-plane: | |
| - 'apps/control-plane/**' | |
| web: | |
| - 'apps/web/**' | |
| - 'packages/ui/**' | |
| - 'tooling/typescript/**' | |
| - 'package.json' | |
| - 'bun.lock' | |
| - name: Set Matrix | |
| id: set-matrix | |
| run: | | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| if [[ "${{ github.event.inputs.containers }}" == "all" ]]; then | |
| echo 'matrix=["core","query-service","mcp-server","control-plane","web"]' >> $GITHUB_OUTPUT | |
| else | |
| # Convert comma-separated to JSON array | |
| containers=$(echo '${{ github.event.inputs.containers }}' | tr ',' '\n' | jq -R . | jq -s .) | |
| echo "matrix=$containers" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| # Build matrix from changed paths | |
| matrix='[]' | |
| if [[ "${{ steps.filter.outputs.core }}" == "true" ]]; then | |
| matrix=$(echo $matrix | jq '. + ["core"]') | |
| fi | |
| if [[ "${{ steps.filter.outputs.query-service }}" == "true" ]]; then | |
| matrix=$(echo $matrix | jq '. + ["query-service"]') | |
| fi | |
| if [[ "${{ steps.filter.outputs.mcp-server }}" == "true" ]]; then | |
| matrix=$(echo $matrix | jq '. + ["mcp-server"]') | |
| fi | |
| if [[ "${{ steps.filter.outputs.control-plane }}" == "true" ]]; then | |
| matrix=$(echo $matrix | jq '. + ["control-plane"]') | |
| fi | |
| if [[ "${{ steps.filter.outputs.web }}" == "true" ]]; then | |
| matrix=$(echo $matrix | jq '. + ["web"]') | |
| fi | |
| # If no changes detected (e.g., workflow file change), test all | |
| if [[ "$matrix" == "[]" ]]; then | |
| matrix='["core","query-service","mcp-server","control-plane","web"]' | |
| fi | |
| echo "matrix=$matrix" >> $GITHUB_OUTPUT | |
| fi | |
| # =========================================================================== | |
| # Build and Test Containers | |
| # =========================================================================== | |
| build: | |
| name: Build ${{ matrix.container }} | |
| needs: changes | |
| runs-on: ubuntu-latest | |
| if: ${{ needs.changes.outputs.matrix != '[]' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| container: ${{ fromJson(needs.changes.outputs.matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Set build context and dockerfile | |
| id: config | |
| run: | | |
| case "${{ matrix.container }}" in | |
| core) | |
| echo "context=apps/core" >> $GITHUB_OUTPUT | |
| echo "dockerfile=apps/core/Dockerfile" >> $GITHUB_OUTPUT | |
| echo "image=chronos-core" >> $GITHUB_OUTPUT | |
| echo "port=3900" >> $GITHUB_OUTPUT | |
| ;; | |
| query-service) | |
| echo "context=apps/query-service" >> $GITHUB_OUTPUT | |
| echo "dockerfile=apps/query-service/Dockerfile" >> $GITHUB_OUTPUT | |
| echo "image=chronos-query-service" >> $GITHUB_OUTPUT | |
| echo "port=4000" >> $GITHUB_OUTPUT | |
| ;; | |
| mcp-server) | |
| echo "context=apps/mcp-server-elixir" >> $GITHUB_OUTPUT | |
| echo "dockerfile=apps/mcp-server-elixir/Dockerfile" >> $GITHUB_OUTPUT | |
| echo "image=chronos-mcp-server" >> $GITHUB_OUTPUT | |
| echo "port=4001" >> $GITHUB_OUTPUT | |
| ;; | |
| control-plane) | |
| echo "context=apps/control-plane" >> $GITHUB_OUTPUT | |
| echo "dockerfile=apps/control-plane/Dockerfile" >> $GITHUB_OUTPUT | |
| echo "image=chronos-control-plane" >> $GITHUB_OUTPUT | |
| echo "port=8080" >> $GITHUB_OUTPUT | |
| ;; | |
| web) | |
| echo "context=." >> $GITHUB_OUTPUT | |
| echo "dockerfile=apps/web/Dockerfile" >> $GITHUB_OUTPUT | |
| echo "image=chronos-web" >> $GITHUB_OUTPUT | |
| echo "port=3000" >> $GITHUB_OUTPUT | |
| ;; | |
| esac | |
| - name: Build image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ${{ steps.config.outputs.context }} | |
| file: ${{ steps.config.outputs.dockerfile }} | |
| push: false | |
| load: true | |
| tags: ${{ steps.config.outputs.image }}:test | |
| cache-from: type=gha,scope=${{ matrix.container }} | |
| cache-to: type=gha,mode=max,scope=${{ matrix.container }} | |
| build-args: | | |
| VERSION=${{ github.sha }} | |
| REVISION=${{ github.sha }} | |
| BUILDTIME=${{ github.event.head_commit.timestamp }} | |
| - name: Check image size | |
| run: | | |
| SIZE=$(docker images --format "{{.Size}}" ${{ steps.config.outputs.image }}:test) | |
| echo "### 📦 Image Size: $SIZE" >> $GITHUB_STEP_SUMMARY | |
| # Convert to MB for comparison | |
| SIZE_MB=$(docker images --format "{{.Size}}" ${{ steps.config.outputs.image }}:test | \ | |
| awk '{ | |
| if (index($0, "GB") > 0) { gsub("GB", "", $0); print $0 * 1024 } | |
| else if (index($0, "MB") > 0) { gsub("MB", "", $0); print $0 } | |
| else if (index($0, "KB") > 0) { gsub("KB", "", $0); print $0 / 1024 } | |
| else { print 0 } | |
| }') | |
| if (( $(echo "$SIZE_MB > 500" | bc -l) )); then | |
| echo "⚠️ Warning: Image size ($SIZE) exceeds 500MB threshold" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| - name: Test container startup | |
| run: | | |
| # Start container | |
| CONTAINER_ID=$(docker run -d -p ${{ steps.config.outputs.port }}:${{ steps.config.outputs.port }} \ | |
| ${{ steps.config.outputs.image }}:test) | |
| # Wait for container to be ready | |
| MAX_WAIT=60 | |
| WAITED=0 | |
| while [ $WAITED -lt $MAX_WAIT ]; do | |
| STATUS=$(docker inspect --format='{{.State.Status}}' $CONTAINER_ID) | |
| if [ "$STATUS" = "exited" ]; then | |
| EXIT_CODE=$(docker inspect --format='{{.State.ExitCode}}' $CONTAINER_ID) | |
| echo "❌ Container exited with code $EXIT_CODE" >> $GITHUB_STEP_SUMMARY | |
| docker logs $CONTAINER_ID | |
| docker rm -f $CONTAINER_ID | |
| exit 1 | |
| fi | |
| # Check if container has healthcheck | |
| HEALTH=$(docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}no-healthcheck{{end}}' $CONTAINER_ID) | |
| if [ "$HEALTH" = "healthy" ] || [ "$HEALTH" = "no-healthcheck" ]; then | |
| if [ "$STATUS" = "running" ]; then | |
| echo "✅ Container started successfully in ${WAITED}s" >> $GITHUB_STEP_SUMMARY | |
| docker rm -f $CONTAINER_ID | |
| exit 0 | |
| fi | |
| fi | |
| sleep 1 | |
| WAITED=$((WAITED + 1)) | |
| done | |
| echo "❌ Container failed to start within ${MAX_WAIT}s" >> $GITHUB_STEP_SUMMARY | |
| docker logs $CONTAINER_ID | |
| docker rm -f $CONTAINER_ID | |
| exit 1 | |
| - name: Run Trivy vulnerability scan | |
| uses: aquasecurity/trivy-action@master | |
| with: | |
| image-ref: '${{ steps.config.outputs.image }}:test' | |
| format: 'sarif' | |
| output: 'trivy-results.sarif' | |
| severity: 'CRITICAL,HIGH' | |
| exit-code: '0' # Don't fail on vulnerabilities, just report | |
| - name: Upload Trivy scan results | |
| uses: github/codeql-action/upload-sarif@v3 | |
| if: always() | |
| with: | |
| sarif_file: 'trivy-results.sarif' | |
| - name: Security scan summary | |
| run: | | |
| if [ -f trivy-results.sarif ]; then | |
| CRITICAL=$(cat trivy-results.sarif | jq '[.runs[].results[] | select(.level == "error")] | length') | |
| HIGH=$(cat trivy-results.sarif | jq '[.runs[].results[] | select(.level == "warning")] | length') | |
| if [ "$CRITICAL" -gt 0 ]; then | |
| echo "⚠️ Found $CRITICAL critical vulnerabilities" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ "$HIGH" -gt 0 ]; then | |
| echo "⚠️ Found $HIGH high vulnerabilities" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ "$CRITICAL" -eq 0 ] && [ "$HIGH" -eq 0 ]; then | |
| echo "✅ No critical or high vulnerabilities found" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| fi | |
| # =========================================================================== | |
| # Docker Compose Integration Test | |
| # =========================================================================== | |
| integration: | |
| name: Integration Test | |
| needs: [changes, build] | |
| runs-on: ubuntu-latest | |
| if: | | |
| always() && | |
| needs.build.result == 'success' && | |
| (contains(fromJson(needs.changes.outputs.matrix), 'core') || | |
| contains(fromJson(needs.changes.outputs.matrix), 'query-service')) | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Build all services | |
| run: | | |
| docker compose build core query-service | |
| - name: Start services | |
| run: | | |
| docker compose up -d core | |
| # Wait for core to be healthy | |
| timeout 60 bash -c 'until docker compose exec -T core wget --spider -q http://localhost:3900/health; do sleep 2; done' | |
| echo "✅ Core service is healthy" >> $GITHUB_STEP_SUMMARY | |
| - name: Run integration tests | |
| run: | | |
| # Basic health check | |
| curl -sf http://localhost:3900/health || exit 1 | |
| # Test event creation | |
| RESPONSE=$(curl -sf -X POST http://localhost:3900/api/v1/events \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"stream_id":"test-stream","event_type":"TestEvent","data":{"message":"hello"}}') | |
| if echo "$RESPONSE" | jq -e '.event_id' > /dev/null; then | |
| echo "✅ Event creation test passed" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "❌ Event creation test failed" >> $GITHUB_STEP_SUMMARY | |
| exit 1 | |
| fi | |
| - name: Cleanup | |
| if: always() | |
| run: docker compose down -v | |
| # =========================================================================== | |
| # Summary Report | |
| # =========================================================================== | |
| summary: | |
| name: Test Summary | |
| needs: [changes, build, integration] | |
| runs-on: ubuntu-latest | |
| if: always() | |
| steps: | |
| - name: Generate Summary | |
| run: | | |
| echo "# 🐳 Container CI Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Container | Build | Startup | Security |" >> $GITHUB_STEP_SUMMARY | |
| echo "|-----------|-------|---------|----------|" >> $GITHUB_STEP_SUMMARY | |
| # This is a simplified summary - actual results come from build job | |
| if [ "${{ needs.build.result }}" == "success" ]; then | |
| echo "| All | ✅ | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "| All | ❌ | - | - |" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ needs.integration.result }}" == "success" ]; then | |
| echo "**Integration Tests:** ✅ Passed" >> $GITHUB_STEP_SUMMARY | |
| elif [ "${{ needs.integration.result }}" == "skipped" ]; then | |
| echo "**Integration Tests:** ⏭️ Skipped" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "**Integration Tests:** ❌ Failed" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| - name: Check overall status | |
| if: needs.build.result != 'success' | |
| run: exit 1 |