medishield / .github /workflows /deploy.yml
sriny2131's picture
deploy: sync from local repo
397b3e6 verified
Raw
History Blame Contribute Delete
6.49 kB
name: Deploy to Azure Container Apps
# Manual-only β€” push-to-main now deploys to Hugging Face Spaces
# (see deploy-huggingface.yml). Use this workflow to refresh the Azure
# fallback when needed (`gh workflow run "Deploy to Azure Container Apps"`).
on:
workflow_dispatch:
env:
RESOURCE_GROUP: medishield-rg
ACR_NAME: medishieldacr27529
CONTAINER_APP_NAME: medishield-classifier
IMAGE_TAG: ${{ github.sha }}
permissions:
id-token: write
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"
- name: Install dependencies
run: uv sync --frozen
- name: Run tests
run: uv run python -m pytest tests/ -q --tb=short
env:
GOOGLE_API_KEY: dummy-key-for-tests
LANGCHAIN_TRACING_V2: "false"
deploy:
needs: test # only deploy if tests pass
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
# ── Azure login via OIDC (no long-lived secrets) ──────────────────────
- name: Azure login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
# ── Set up buildx for linux/amd64 cross-platform builds ──────────────
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# ── Build linux/amd64 image (load into local Docker for smoke test) ──
- name: Build image (linux/amd64)
run: |
docker buildx build --platform linux/amd64 \
-t ${{ env.ACR_NAME }}.azurecr.io/classifier:${{ env.IMAGE_TAG }} \
-t ${{ env.ACR_NAME }}.azurecr.io/classifier:latest \
--load .
# ── Pre-push smoke test: image actually starts and /health responds ──
- name: Smoke test image locally
run: |
docker run -d --name smoke -p 8000:8000 \
-e GOOGLE_API_KEY=dummy-key-for-smoke-test \
-e PYTHONUNBUFFERED=1 \
${{ env.ACR_NAME }}.azurecr.io/classifier:${{ env.IMAGE_TAG }}
for i in $(seq 1 30); do
if curl -sf http://localhost:8000/health; then
echo "βœ… Local image /health OK"
docker rm -f smoke
exit 0
fi
sleep 2
done
echo "❌ Local image failed to become healthy"
docker logs smoke
docker rm -f smoke
exit 1
# ── Push to ACR (only after local smoke test passed) ─────────────────
- name: Push image to ACR
run: |
ACR_PASSWORD=$(az acr credential show --name ${{ env.ACR_NAME }} --query "passwords[0].value" -o tsv)
echo "$ACR_PASSWORD" | docker login ${{ env.ACR_NAME }}.azurecr.io \
--username ${{ env.ACR_NAME }} --password-stdin
docker push ${{ env.ACR_NAME }}.azurecr.io/classifier:${{ env.IMAGE_TAG }}
docker push ${{ env.ACR_NAME }}.azurecr.io/classifier:latest
# ── Update secrets in Container Apps ─────────────────────────────────
- name: Update Container App secrets
run: |
SECRETS="google-api-key=${{ secrets.GOOGLE_API_KEY }}"
if [ -n "${{ secrets.LANGCHAIN_API_KEY }}" ]; then
SECRETS="$SECRETS langchain-api-key=${{ secrets.LANGCHAIN_API_KEY }}"
fi
az containerapp secret set \
--name ${{ env.CONTAINER_APP_NAME }} \
--resource-group ${{ env.RESOURCE_GROUP }} \
--secrets $SECRETS
# ── Rolling deploy to Container Apps ──────────────────────────────────
- name: Deploy to Container Apps
run: |
ENV_VARS="GOOGLE_API_KEY=secretref:google-api-key PYTHONUNBUFFERED=1"
if [ -n "${{ secrets.LANGCHAIN_API_KEY }}" ]; then
ENV_VARS="$ENV_VARS LANGCHAIN_TRACING_V2=true LANGCHAIN_API_KEY=secretref:langchain-api-key LANGCHAIN_PROJECT=medishield-classification"
fi
az containerapp update \
--name ${{ env.CONTAINER_APP_NAME }} \
--resource-group ${{ env.RESOURCE_GROUP }} \
--image ${{ env.ACR_NAME }}.azurecr.io/classifier:${{ env.IMAGE_TAG }} \
--set-env-vars $ENV_VARS
# ── Smoke test the live endpoint ──────────────────────────────────────
- name: Smoke test live /health
run: |
APP_URL=$(az containerapp show \
--name ${{ env.CONTAINER_APP_NAME }} \
--resource-group ${{ env.RESOURCE_GROUP }} \
--query "properties.configuration.ingress.fqdn" -o tsv)
curl --fail --retry 5 --retry-delay 10 "https://$APP_URL/health"
echo "βœ… /health OK"
# ── Functional test: POST /classify with a real document ────────────
- name: Smoke test live /classify
run: |
APP_URL=$(az containerapp show \
--name ${{ env.CONTAINER_APP_NAME }} \
--resource-group ${{ env.RESOURCE_GROUP }} \
--query "properties.configuration.ingress.fqdn" -o tsv)
SAMPLE=$(ls dataset/*.png 2>/dev/null | head -1)
if [ -z "$SAMPLE" ]; then
echo "No sample image in dataset/, skipping functional test"
exit 0
fi
HTTP_CODE=$(curl -s -o /tmp/resp.json -w "%{http_code}" \
-X POST "https://$APP_URL/classify" -F "files=@$SAMPLE")
if [ "$HTTP_CODE" != "200" ]; then
echo "❌ /classify returned $HTTP_CODE"
cat /tmp/resp.json
exit 1
fi
echo "βœ… /classify returned 200"
cat /tmp/resp.json
- name: Print app URL
run: |
APP_URL=$(az containerapp show \
--name ${{ env.CONTAINER_APP_NAME }} \
--resource-group ${{ env.RESOURCE_GROUP }} \
--query "properties.configuration.ingress.fqdn" -o tsv)
echo "πŸš€ Deployed: https://$APP_URL"