Task 1 Fully Complete #2
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: ProdSegmenter CI/CD Pipeline | |
on: | |
push: | |
branches: [ main, develop ] | |
pull_request: | |
branches: [ main ] | |
workflow_dispatch: | |
env: | |
PYTHON_VERSION: '3.10' | |
AZURE_FUNCTIONAPP_NAME: 'func-prodsegmenter-inference' | |
AZURE_WEBAPP_NAME: 'app-prodsegmenter-frontend' | |
jobs: | |
# Code Quality and Linting | |
lint: | |
runs-on: ubuntu-latest | |
steps: | |
- uses: actions/checkout@v3 | |
- name: Set up Python | |
uses: actions/setup-python@v4 | |
with: | |
python-version: ${{ env.PYTHON_VERSION }} | |
- name: Install dependencies | |
run: | | |
python -m pip install --upgrade pip | |
pip install black flake8 mypy | |
pip install -r requirements.txt | |
- name: Run Black formatter check | |
run: black --check --diff . | |
- name: Run Flake8 linter | |
run: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics | |
- name: Run MyPy type checker | |
run: mypy training/ deployment/ --ignore-missing-imports | |
# Unit and Integration Tests | |
test: | |
runs-on: ubuntu-latest | |
needs: lint | |
strategy: | |
matrix: | |
python-version: ['3.9', '3.10', '3.11'] | |
steps: | |
- uses: actions/checkout@v3 | |
- 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 }}-pip-${{ hashFiles('**/requirements.txt') }} | |
restore-keys: | | |
${{ runner.os }}-pip- | |
- name: Install dependencies | |
run: | | |
python -m pip install --upgrade pip | |
pip install pytest pytest-cov pytest-mock pytest-xdist | |
pip install -r requirements.txt | |
- name: Run unit tests | |
run: | | |
pytest tests/ -v --cov=training --cov=deployment \ | |
--cov-report=xml --cov-report=html \ | |
--junitxml=test-results.xml \ | |
-m "not slow and not integration" | |
- name: Upload coverage to Codecov | |
uses: codecov/codecov-action@v3 | |
with: | |
file: ./coverage.xml | |
flags: unittests | |
name: codecov-umbrella | |
- name: Upload test results | |
uses: actions/upload-artifact@v3 | |
if: always() | |
with: | |
name: test-results-${{ matrix.python-version }} | |
path: | | |
test-results.xml | |
htmlcov/ | |
# Integration Tests (with Azure services mocked) | |
integration-test: | |
runs-on: ubuntu-latest | |
needs: test | |
steps: | |
- uses: actions/checkout@v3 | |
- name: Set up Python | |
uses: actions/setup-python@v4 | |
with: | |
python-version: ${{ env.PYTHON_VERSION }} | |
- name: Install dependencies | |
run: | | |
python -m pip install --upgrade pip | |
pip install pytest pytest-mock | |
pip install -r requirements.txt | |
- name: Run integration tests | |
run: | | |
pytest tests/integration/ -v \ | |
--junitxml=integration-test-results.xml \ | |
-m "integration" | |
- name: Upload integration test results | |
uses: actions/upload-artifact@v3 | |
if: always() | |
with: | |
name: integration-test-results | |
path: integration-test-results.xml | |
# Security Scanning | |
security: | |
runs-on: ubuntu-latest | |
needs: lint | |
steps: | |
- uses: actions/checkout@v3 | |
- name: Set up Python | |
uses: actions/setup-python@v4 | |
with: | |
python-version: ${{ env.PYTHON_VERSION }} | |
- name: Install security tools | |
run: | | |
python -m pip install --upgrade pip | |
pip install bandit safety | |
pip install -r requirements.txt | |
- name: Run Bandit security linter | |
run: bandit -r training/ deployment/ -f json -o bandit-report.json | |
continue-on-error: true | |
- name: Run Safety vulnerability check | |
run: safety check --json --output safety-report.json | |
continue-on-error: true | |
- name: Upload security reports | |
uses: actions/upload-artifact@v3 | |
if: always() | |
with: | |
name: security-reports | |
path: | | |
bandit-report.json | |
safety-report.json | |
# Build Docker Images | |
build-images: | |
runs-on: ubuntu-latest | |
needs: [test, integration-test] | |
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' | |
steps: | |
- uses: actions/checkout@v3 | |
- name: Set up Docker Buildx | |
uses: docker/setup-buildx-action@v2 | |
- name: Login to Azure Container Registry | |
uses: azure/docker-login@v1 | |
with: | |
login-server: ${{ secrets.AZURE_CONTAINER_REGISTRY }} | |
username: ${{ secrets.AZURE_ACR_USERNAME }} | |
password: ${{ secrets.AZURE_ACR_PASSWORD }} | |
- name: Build and push Azure Function image | |
uses: docker/build-push-action@v4 | |
with: | |
context: ./deployment/azure_function | |
push: true | |
tags: | | |
${{ secrets.AZURE_CONTAINER_REGISTRY }}/prodsegmenter-api:latest | |
${{ secrets.AZURE_CONTAINER_REGISTRY }}/prodsegmenter-api:${{ github.sha }} | |
cache-from: type=gha | |
cache-to: type=gha,mode=max | |
- name: Build and push Streamlit frontend image | |
uses: docker/build-push-action@v4 | |
with: | |
context: ./deployment/streamlit_frontend | |
push: true | |
tags: | | |
${{ secrets.AZURE_CONTAINER_REGISTRY }}/prodsegmenter-frontend:latest | |
${{ secrets.AZURE_CONTAINER_REGISTRY }}/prodsegmenter-frontend:${{ github.sha }} | |
cache-from: type=gha | |
cache-to: type=gha,mode=max | |
# Deploy to Development Environment | |
deploy-dev: | |
runs-on: ubuntu-latest | |
needs: build-images | |
if: github.ref == 'refs/heads/develop' | |
environment: development | |
steps: | |
- uses: actions/checkout@v3 | |
- name: Azure Login | |
uses: azure/login@v1 | |
with: | |
creds: ${{ secrets.AZURE_CREDENTIALS }} | |
- name: Deploy Azure Function (Dev) | |
uses: Azure/functions-action@v1 | |
with: | |
app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }}-dev | |
package: './deployment/azure_function' | |
respect-funcignore: true | |
- name: Deploy Streamlit App (Dev) | |
uses: azure/webapps-deploy@v2 | |
with: | |
app-name: ${{ env.AZURE_WEBAPP_NAME }}-dev | |
images: ${{ secrets.AZURE_CONTAINER_REGISTRY }}/prodsegmenter-frontend:${{ github.sha }} | |
- name: Run smoke tests | |
run: | | |
# Wait for deployment to be ready | |
sleep 60 | |
# Test function app health | |
curl -f https://${{ env.AZURE_FUNCTIONAPP_NAME }}-dev.azurewebsites.net/api/health | |
# Test web app health | |
curl -f https://${{ env.AZURE_WEBAPP_NAME }}-dev.azurewebsites.net | |
# Deploy to Production Environment | |
deploy-prod: | |
runs-on: ubuntu-latest | |
needs: [build-images, deploy-dev] | |
if: github.ref == 'refs/heads/main' | |
environment: production | |
steps: | |
- uses: actions/checkout@v3 | |
- name: Azure Login | |
uses: azure/login@v1 | |
with: | |
creds: ${{ secrets.AZURE_CREDENTIALS }} | |
- name: Deploy Azure Function (Prod) | |
uses: Azure/functions-action@v1 | |
with: | |
app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }} | |
package: './deployment/azure_function' | |
respect-funcignore: true | |
- name: Deploy Streamlit App (Prod) | |
uses: azure/webapps-deploy@v2 | |
with: | |
app-name: ${{ env.AZURE_WEBAPP_NAME }} | |
images: ${{ secrets.AZURE_CONTAINER_REGISTRY }}/prodsegmenter-frontend:${{ github.sha }} | |
- name: Run production smoke tests | |
run: | | |
# Wait for deployment to be ready | |
sleep 60 | |
# Test function app health | |
curl -f https://${{ env.AZURE_FUNCTIONAPP_NAME }}.azurewebsites.net/api/health | |
# Test web app health | |
curl -f https://${{ env.AZURE_WEBAPP_NAME }}.azurewebsites.net | |
- name: Create GitHub Release | |
if: success() | |
uses: actions/create-release@v1 | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
with: | |
tag_name: v${{ github.run_number }} | |
release_name: Release v${{ github.run_number }} | |
body: | | |
Automated release for commit ${{ github.sha }} | |
Changes in this release: | |
- See commit history for details | |
Deployment: | |
- Azure Function: ${{ env.AZURE_FUNCTIONAPP_NAME }} | |
- Streamlit App: ${{ env.AZURE_WEBAPP_NAME }} | |
draft: false | |
prerelease: false | |
# Model Training Job (Triggered separately) | |
train-model: | |
runs-on: ubuntu-latest | |
if: github.event_name == 'workflow_dispatch' | |
environment: production | |
steps: | |
- uses: actions/checkout@v3 | |
- name: Azure Login | |
uses: azure/login@v1 | |
with: | |
creds: ${{ secrets.AZURE_CREDENTIALS }} | |
- name: Set up Python | |
uses: actions/setup-python@v4 | |
with: | |
python-version: ${{ env.PYTHON_VERSION }} | |
- name: Install Azure ML CLI | |
run: | | |
az extension add -n ml -y | |
az extension update -n ml | |
- name: Submit training job | |
run: | | |
az ml job create --file .azure/training-job.yml \ | |
--workspace-name ml-prodsegmenter \ | |
--resource-group rg-prodsegmenter | |
- name: Monitor training job | |
run: | | |
# Get job name from submission | |
JOB_NAME=$(az ml job list --workspace-name ml-prodsegmenter \ | |
--resource-group rg-prodsegmenter \ | |
--query "[0].name" -o tsv) | |
# Wait for completion (max 4 hours) | |
az ml job show --name $JOB_NAME \ | |
--workspace-name ml-prodsegmenter \ | |
--resource-group rg-prodsegmenter \ | |
--query "status" | |
# Performance Testing | |
performance-test: | |
runs-on: ubuntu-latest | |
needs: deploy-prod | |
if: github.ref == 'refs/heads/main' | |
steps: | |
- uses: actions/checkout@v3 | |
- name: Set up Python | |
uses: actions/setup-python@v4 | |
with: | |
python-version: ${{ env.PYTHON_VERSION }} | |
- name: Install load testing tools | |
run: | | |
pip install locust requests | |
- name: Run performance tests | |
run: | | |
cd tests/performance | |
locust -f load_test.py \ | |
--host https://${{ env.AZURE_FUNCTIONAPP_NAME }}.azurewebsites.net \ | |
--users 50 \ | |
--spawn-rate 5 \ | |
--run-time 300s \ | |
--headless \ | |
--html performance-report.html | |
- name: Upload performance report | |
uses: actions/upload-artifact@v3 | |
with: | |
name: performance-report | |
path: tests/performance/performance-report.html | |
# Cleanup | |
cleanup: | |
runs-on: ubuntu-latest | |
needs: [deploy-prod, performance-test] | |
if: always() | |
steps: | |
- name: Clean up old container images | |
uses: azure/login@v1 | |
with: | |
creds: ${{ secrets.AZURE_CREDENTIALS }} | |
- name: Delete old images from ACR | |
run: | | |
# Keep only last 10 images | |
az acr repository show-manifests \ | |
--name ${{ secrets.AZURE_CONTAINER_REGISTRY }} \ | |
--repository prodsegmenter-api \ | |
--orderby time_desc \ | |
--query "[10:].digest" -o tsv | \ | |
xargs -I {} az acr repository delete \ | |
--name ${{ secrets.AZURE_CONTAINER_REGISTRY }} \ | |
--image prodsegmenter-api@{} --yes |