coverage init

This commit is contained in:
2025-11-03 20:15:52 +05:30
commit abd37403f5
5 changed files with 279 additions and 0 deletions

143
.drone.yml Normal file
View File

@@ -0,0 +1,143 @@
---
kind: pipeline
type: docker
name: default
platform:
os: linux
arch: arm64
workspace:
path: /drone/src
volumes:
- name: dockersock
host:
path: /var/run/docker.sock
steps:
- name: fetch-tags
image: docker:24
volumes:
- name: dockersock
path: /var/run/docker.sock
commands:
- apk add --no-cache git
- git fetch --tags
- |
# Get latest Git tag and trim newline
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null | tr -d '\n')
echo "Latest Git tag fetched: $LATEST_TAG"
# Save to file for downstream steps
echo "$LATEST_TAG" > /drone/src/LATEST_TAG.txt
# Read back for verification
IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n')
echo "Image tag read from file: $IMAGE_TAG"
# Validate
if [ -z "$IMAGE_TAG" ]; then
echo "❌ No git tags found! Cannot continue."
exit 1
fi
- name: check-remote-image
image: docker:24
volumes:
- name: dockersock
path: /var/run/docker.sock
commands:
- IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n')
- echo "Checking if apps/coverage:$IMAGE_TAG exists on remote Docker..."
- echo "Existing Docker tags for apps/coverage:"
- docker images --format "{{.Repository}}:{{.Tag}}" | grep "^apps/coverage" || echo "(none)"
- |
if docker image inspect apps/coverage:$IMAGE_TAG > /dev/null 2>&1; then
echo "✅ Docker image apps/coverage:$IMAGE_TAG already exists — skipping build"
exit 78
else
echo "⚙️ Docker image apps/coverage:$IMAGE_TAG not found — proceeding to build..."
fi
- name: build-image
image: docker:24
volumes:
- name: dockersock
path: /var/run/docker.sock
commands:
- IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n')
- echo "🔨 Building Docker image apps/coverage:$IMAGE_TAG ..."
- |
docker build \
--network=host \
-t apps/coverage:$IMAGE_TAG \
-t apps/coverage:latest \
/drone/src
- name: push-image
image: docker:24
environment:
REGISTRY_HOST:
from_secret: REGISTRY_HOST
REGISTRY_USER:
from_secret: REGISTRY_USER
REGISTRY_PASS:
from_secret: REGISTRY_PASS
volumes:
- name: dockersock
path: /var/run/docker.sock
commands:
- IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n')
- echo "🔑 Logging into registry $REGISTRY_HOST ..."
- echo "$REGISTRY_PASS" | docker login $REGISTRY_HOST -u "$REGISTRY_USER" --password-stdin
- echo "🏷️ Tagging images with registry prefix..."
- docker tag apps/coverage:$IMAGE_TAG $REGISTRY_HOST/apps/coverage:$IMAGE_TAG
- docker tag apps/coverage:$IMAGE_TAG $REGISTRY_HOST/apps/coverage:latest
- echo "📤 Pushing apps/coverage:$IMAGE_TAG ..."
- docker push $REGISTRY_HOST/apps/coverage:$IMAGE_TAG
- echo "📤 Pushing apps/coverage:latest ..."
- docker push $REGISTRY_HOST/apps/coverage:latest
- name: stop-old
image: docker:24
volumes:
- name: dockersock
path: /var/run/docker.sock
commands:
- echo "🛑 Stopping old container..."
- docker rm -f coverage || true
- name: run-container
image: docker:24
volumes:
- name: dockersock
path: /var/run/docker.sock
environment:
MONGO_HOST:
from_secret: MONGO_HOST
MONGO_USER:
from_secret: MONGO_USER
MONGO_PASS:
from_secret: MONGO_PASS
MONGO_PORT:
from_secret: MONGO_PORT
commands:
- IMAGE_TAG=$(cat /drone/src/LATEST_TAG.txt | tr -d '\n')
- echo "🚀 Starting container apps/coverage:$IMAGE_TAG ..."
- |
docker run -d \
--name coverage \
-p 9002:8000 \
--restart always \
apps/coverage:$IMAGE_TAG
# Trigger rules
trigger:
event:
- tag
- custom

21
.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
# Virtual environments
venv/
env/
.venv
# PyCharm
.idea/
# Environment variables
.env
.env.local
# OS
.DS_Store
Thumbs.db

55
Dockerfile Normal file
View File

@@ -0,0 +1,55 @@
# ===========================
# Stage 1: Build environment
# ===========================
FROM python:3.13-slim AS builder
ENV PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential curl && \
pip install --upgrade pip uv && \
rm -rf /var/lib/apt/lists/*
# Copy requirement file first for better caching
COPY requirements.txt .
# ✅ Install dependencies
RUN pip install -r requirements.txt
# Copy source code
COPY . .
# ===========================
# Stage 2: Runtime environment
# ===========================
FROM python:3.13-slim
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
APP_HOME=/app
WORKDIR $APP_HOME
# Install runtime dependency (curl for healthcheck)
RUN apt-get update && apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
# ✅ Copy Python and pip-installed packages from builder
COPY --from=builder /usr/local/lib/python3.13 /usr/local/lib/python3.13
COPY --from=builder /usr/local/bin /usr/local/bin
# Copy application code
COPY . .
# Expose FastAPI port
EXPOSE 8000
# Healthcheck endpoint
HEALTHCHECK CMD curl --fail http://localhost:8000/health || exit 1
# ✅ Start FastAPI app
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

58
main.py Normal file
View File

@@ -0,0 +1,58 @@
from fastapi import FastAPI, HTTPException, Query
from fastapi.responses import JSONResponse
from pathlib import Path
import xml.etree.ElementTree as ET
app = FastAPI()
@app.get("/health")
async def health_check():
return {"status": "ok"}
def parse_coverage_percentage(coverage_file: Path) -> float:
"""Parse the overall coverage percentage from coverage.xml."""
tree = ET.parse(coverage_file)
root = tree.getroot()
line_rate = float(root.attrib.get("line-rate", 0))
return round(line_rate * 100, 2)
def get_color(pct: float) -> str:
"""Return badge color based on coverage."""
if pct >= 90:
return "brightgreen"
if pct >= 75:
return "green"
if pct >= 60:
return "yellow"
if pct >= 40:
return "orange"
return "red"
@app.get("/badge", response_class=JSONResponse)
async def get_coverage_badge(project: str = Query(..., description="Project folder name")):
"""
Return a Shields.io-compatible badge JSON for the given project's coverage.
Example:
/badge?project=mongo-ops
Use with Shields.io:
https://img.shields.io/endpoint?url=https://api.aetoskia.com/coverage/badge?project=mongo-ops
"""
project_dir = Path.cwd() / project
coverage_file = project_dir / "coverage.xml"
if not coverage_file.exists():
raise HTTPException(status_code=404, detail=f"No coverage.xml found in '{project}'")
pct = parse_coverage_percentage(coverage_file)
return {
"schemaVersion": 1,
"label": f"{project} coverage",
"message": f"{pct}%",
"color": get_color(pct)
}

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
fastapi==0.120.4
uvicorn==0.38.0