Refactor error handling and cleanup dependencies #53
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
| name: CI/CD Pipeline | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| pull_request: | |
| branches: [ main ] | |
| release: | |
| types: [ published ] | |
| env: | |
| PYTHON_VERSION: '3.11' | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| jobs: | |
| # Code quality and security checks | |
| code-quality: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Cache pip dependencies | |
| uses: actions/cache@v3 | |
| with: | |
| path: ~/.cache/pip | |
| key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pip- | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| pip install -r requirements-dev.txt | |
| - name: Code formatting check (Black) | |
| run: black --check --diff src/ tests/ benchmarks/ | |
| - name: Import sorting check (isort) | |
| run: isort --check-only --diff src/ tests/ benchmarks/ | |
| - name: Linting (Flake8) | |
| run: flake8 src/ tests/ benchmarks/ --max-line-length=100 --ignore=E203,W503 | |
| - name: Type checking (MyPy) | |
| run: mypy src/ --ignore-missing-imports | |
| - name: Security check (Bandit) | |
| run: bandit -r src/ -f json -o bandit-report.json | |
| - name: Dependency vulnerability check (Safety) | |
| run: safety check --json --output safety-report.json | |
| - name: Secret detection | |
| run: detect-secrets scan --all-files --baseline .secrets.baseline || echo "No secrets baseline found, creating one" && detect-secrets scan --all-files --baseline .secrets.baseline --force-use-all-plugins | |
| - name: Upload security reports | |
| uses: actions/upload-artifact@v3 | |
| if: always() | |
| with: | |
| name: security-reports | |
| path: | | |
| bandit-report.json | |
| safety-report.json | |
| # Unit and integration tests | |
| test: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| python-version: ['3.9', '3.10', '3.11'] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Cache pip dependencies | |
| uses: actions/cache@v3 | |
| with: | |
| path: ~/.cache/pip | |
| key: ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('requirements.txt') }} | |
| restore-keys: | | |
| ${{ runner.os }}-${{ matrix.python-version }}-pip- | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| pip install -r requirements-dev.txt | |
| - name: Run unit tests | |
| run: | | |
| pytest tests/ -v --cov=src --cov-report=xml --cov-report=html --cov-report=term-missing | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v3 | |
| with: | |
| file: ./coverage.xml | |
| flags: unittests | |
| name: codecov-umbrella | |
| fail_ci_if_error: false | |
| - name: Upload test results | |
| uses: actions/upload-artifact@v3 | |
| if: always() | |
| with: | |
| name: test-results-${{ matrix.python-version }} | |
| path: | | |
| coverage.xml | |
| htmlcov/ | |
| # Docker build and scan | |
| docker: | |
| runs-on: ubuntu-latest | |
| needs: [code-quality, test] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to Container Registry | |
| if: github.event_name != 'pull_request' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=pr | |
| type=sha,prefix={{branch}}- | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| - name: Build and test Docker image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| target: test | |
| load: true | |
| tags: ensemble:test | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Run Docker security scan | |
| uses: aquasecurity/trivy-action@master | |
| with: | |
| image-ref: 'ensemble:test' | |
| format: 'sarif' | |
| output: 'trivy-results.sarif' | |
| - name: Upload Trivy scan results | |
| uses: github/codeql-action/upload-sarif@v2 | |
| if: always() | |
| with: | |
| sarif_file: 'trivy-results.sarif' | |
| - name: Build and push production image | |
| if: github.event_name != 'pull_request' | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| target: production | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # Performance and load testing | |
| performance: | |
| runs-on: ubuntu-latest | |
| needs: [docker] | |
| if: github.ref == 'refs/heads/main' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Install performance testing tools | |
| run: | | |
| pip install locust memory-profiler | |
| - name: Create performance test script | |
| run: | | |
| cat > performance_test.py << 'EOF' | |
| import asyncio | |
| import time | |
| import sys | |
| import os | |
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) | |
| from ensemble import main | |
| from unittest.mock import patch, MagicMock | |
| async def mock_ensemble_run(): | |
| """Mock ensemble run for performance testing.""" | |
| mock_config = { | |
| "OPENROUTER_API_KEY": "test-key", | |
| "MODELS": ["test-model-1", "test-model-2"], | |
| "REFINEMENT_MODEL_NAME": "test-refinement", | |
| "PROMPT": "Test prompt for performance" | |
| } | |
| def mock_create_completion(*args, **kwargs): | |
| # Simulate API delay | |
| time.sleep(0.1) | |
| class MockResponse: | |
| choices = [type('', (), {'message': type('', (), {'content': 'Mock response'})})()] | |
| return MockResponse() | |
| mock_client = MagicMock() | |
| mock_client.chat.completions.create = mock_create_completion | |
| with patch('ensemble.load_config', return_value=mock_config), \ | |
| patch('ensemble.init_client', return_value=mock_client), \ | |
| patch('asyncio.to_thread', side_effect=lambda func, *args, **kwargs: func(*args, **kwargs)), \ | |
| patch('builtins.print'), \ | |
| patch('builtins.input', return_value="Test prompt"): | |
| start_time = time.time() | |
| await main() | |
| return time.time() - start_time | |
| async def run_performance_test(): | |
| """Run performance test suite.""" | |
| print("Running performance tests...") | |
| # Warmup run | |
| await mock_ensemble_run() | |
| # Measure multiple runs | |
| durations = [] | |
| for i in range(10): | |
| duration = await mock_ensemble_run() | |
| durations.append(duration) | |
| print(f"Run {i+1}: {duration:.2f}s") | |
| avg_duration = sum(durations) / len(durations) | |
| max_duration = max(durations) | |
| min_duration = min(durations) | |
| print(f"\nPerformance Results:") | |
| print(f"Average duration: {avg_duration:.2f}s") | |
| print(f"Min duration: {min_duration:.2f}s") | |
| print(f"Max duration: {max_duration:.2f}s") | |
| # Performance thresholds | |
| if avg_duration > 10: # 10 seconds average | |
| print("WARNING: Average duration exceeds threshold") | |
| sys.exit(1) | |
| if max_duration > 20: # 20 seconds max | |
| print("WARNING: Maximum duration exceeds threshold") | |
| sys.exit(1) | |
| print("Performance test passed!") | |
| if __name__ == "__main__": | |
| asyncio.run(run_performance_test()) | |
| EOF | |
| - name: Run performance tests | |
| run: python performance_test.py | |
| - name: Memory profiling | |
| run: | | |
| python -m memory_profiler performance_test.py | |
| # Documentation and release | |
| docs-and-release: | |
| runs-on: ubuntu-latest | |
| needs: [code-quality, test, docker] | |
| if: github.event_name == 'release' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Generate API documentation | |
| run: | | |
| pip install pydoc-markdown mkdocs mkdocs-material | |
| mkdir -p docs/api | |
| # Generate module documentation | |
| python -c " | |
| import pydoc | |
| import sys | |
| import os | |
| sys.path.insert(0, 'src') | |
| modules = ['ensemble', 'config', 'validation', 'rate_limiter', 'monitoring', 'health_check'] | |
| for module in modules: | |
| try: | |
| pydoc.writedoc(module) | |
| print(f'Generated docs for {module}') | |
| except Exception as e: | |
| print(f'Failed to generate docs for {module}: {e}') | |
| " | |
| - name: Create release notes | |
| run: | | |
| cat > RELEASE_NOTES.md << 'EOF' | |
| # Release ${{ github.event.release.tag_name }} | |
| ## What's New | |
| ### Features | |
| - Production-ready ensemble AI processing | |
| - Comprehensive input validation and sanitization | |
| - Rate limiting and circuit breaker patterns | |
| - Performance monitoring and health checks | |
| - Docker containerization support | |
| ### Security | |
| - Input sanitization against injection attacks | |
| - Secure configuration management | |
| - Container security scanning | |
| ### Performance | |
| - Parallel model request processing | |
| - Intelligent error handling and retry logic | |
| - Resource usage monitoring | |
| ### DevOps | |
| - Complete CI/CD pipeline | |
| - Docker multi-stage builds | |
| - Automated testing and security scanning | |
| ## Installation | |
| ```bash | |
| # Using Docker | |
| docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }} | |
| # From source | |
| git clone https://github.com/${{ github.repository }}.git | |
| cd ensemble | |
| pip install -r requirements.txt | |
| ``` | |
| ## Documentation | |
| See the [README](README.md) for complete usage instructions. | |
| EOF | |
| - name: Upload release artifacts | |
| uses: actions/upload-artifact@v3 | |
| with: | |
| name: release-${{ github.event.release.tag_name }} | |
| path: | | |
| RELEASE_NOTES.md | |
| docs/ | |
| *.html | |
| # Deployment (example for staging/production) | |
| deploy-staging: | |
| runs-on: ubuntu-latest | |
| needs: [performance] | |
| if: github.ref == 'refs/heads/main' | |
| # environment: staging # Uncomment when staging environment is created in GitHub repo settings | |
| steps: | |
| - name: Deploy to staging | |
| run: | | |
| echo "Deploying to staging environment..." | |
| echo "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main-${{ github.sha }}" | |
| # Add actual deployment commands here | |
| deploy-production: | |
| runs-on: ubuntu-latest | |
| needs: [docs-and-release] | |
| if: github.event_name == 'release' | |
| # environment: production # Uncomment when production environment is created in GitHub repo settings | |
| steps: | |
| - name: Deploy to production | |
| run: | | |
| echo "Deploying to production environment..." | |
| echo "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}" | |
| # Add actual deployment commands here |