Skip to content

Commit 8b5e9ee

Browse files
authored
Merge pull request #18 from gnanarepo/fix/tenantname
add health endpoints
2 parents d2cc567 + e0e99cc commit 8b5e9ee

File tree

4 files changed

+90
-17
lines changed

4 files changed

+90
-17
lines changed

Dockerfile

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
# ==========================================
44
FROM python:3.10-slim AS builder
55

6-
ARG GITHUB_TOKEN
7-
86
WORKDIR /build
97

108
# Install build dependencies
@@ -20,8 +18,10 @@ RUN apt-get update \
2018

2119
COPY requirements.txt .
2220

23-
# CRITICAL: Create .netrc to authenticate HTTPS URLs automatically
24-
RUN echo "machine github.com login ${GITHUB_TOKEN} password x-oauth-basic" > ~/.netrc \
21+
# SECURE FIX: Use a secret mount instead of ARG.
22+
# This mounts the token only for this command, leaving no trace in the image history.
23+
RUN --mount=type=secret,id=github_token \
24+
echo "machine github.com login $(cat /run/secrets/github_token) password x-oauth-basic" > ~/.netrc \
2525
&& chmod 600 ~/.netrc \
2626
&& pip install --user --no-cache-dir -r requirements.txt \
2727
&& rm ~/.netrc
@@ -35,9 +35,13 @@ LABEL maintainer="Engineering Team <engineering@aviso.com>" \
3535
version="1.0.0" \
3636
description="Aviso Core Django Service"
3737

38+
# Create the user first so we can use it for permissions
39+
RUN adduser --disabled-password --gecos '' appuser
40+
3841
ENV PYTHONDONTWRITEBYTECODE=1 \
3942
PYTHONUNBUFFERED=1 \
4043
AVISO_APPS=aviso_core \
44+
# Update PATH so python finds the installed scripts
4145
PATH="/home/appuser/.local/bin:$PATH"
4246

4347
WORKDIR /app
@@ -49,17 +53,15 @@ RUN apt-get update \
4953
libmemcached11 \
5054
&& rm -rf /var/lib/apt/lists/*
5155

52-
RUN adduser --disabled-password --gecos '' appuser
53-
54-
COPY --from=builder /root/.local /home/appuser/.local
55-
56-
COPY . /app
56+
# FIX: Copy artifacts and change ownership immediately
57+
# The files in builder are owned by root (/root/.local).
58+
# We must chown them to appuser when copying to /home/appuser/.local
59+
COPY --from=builder --chown=appuser:appuser /root/.local /home/appuser/.local
5760

58-
RUN chown -R appuser:appuser /app
61+
COPY --chown=appuser:appuser . /app
5962

6063
USER appuser
6164

6265
EXPOSE 8000
6366

64-
# Ensure Gunicorn is installed in requirements.txt!
6567
CMD ["gunicorn", "aviso_core.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3"]

docker-compose.yml

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,33 @@
1-
version: '3.8'
2-
1+
version: "3.8"
32
services:
43
web:
54
image: aviso-core
5+
container_name: aviso-core
66
env_file:
77
- .env
88
ports:
99
- "8000:8000"
10-
# Overrides the CMD in Dockerfile if you want hot-reloading for dev
11-
command: gunicorn aviso_core.wsgi:application --bind 0.0.0.0:8000 --reload
10+
command: gunicorn aviso_core.wsgi:application --bind 0.0.0.0:8000 --reload
11+
healthcheck:
12+
# Ensure 'curl' is installed in your Docker image!
13+
test: ["CMD", "curl", "-f", "http://localhost:8000/gbm/health/"]
14+
interval: 30s
15+
timeout: 10s
16+
retries: 3
17+
start_period: 40s
18+
deploy:
19+
resources:
20+
reservations: # Changed from 'requests'
21+
cpus: "1" # Minimum reserved
22+
memory: 2G # Minimum reserved
23+
limits:
24+
cpus: "2" # Hard cap
25+
memory: 4G # Hard cap
26+
# Restart policy
27+
restart: unless-stopped
28+
# Logging configuration
29+
logging:
30+
driver: "json-file"
31+
options:
32+
max-size: "10m"
33+
max-file: "3"

gbm_apis/api/health.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import logging
2+
from django.http import JsonResponse
3+
from django.views import View
4+
from django.utils.decorators import method_decorator
5+
from django.views.decorators.csrf import csrf_exempt
6+
from django.db import connection
7+
from django.conf import settings
8+
9+
logger = logging.getLogger(f'gnana.{__name__}')
10+
11+
12+
@method_decorator(csrf_exempt, name='dispatch')
13+
class HealthCheckView(View):
14+
def get(self, request):
15+
"""
16+
Health check endpoint that verifies basic application health
17+
Returns 200 if healthy, 503 if unhealthy
18+
"""
19+
try:
20+
health_status = {
21+
"status": "healthy",
22+
"service": "aviso-core",
23+
"version": getattr(settings, 'APP_VERSION', '1.0.0'),
24+
"checks": {}
25+
}
26+
27+
try:
28+
with connection.cursor() as cursor:
29+
cursor.execute("SELECT 1")
30+
health_status["checks"]["database"] = "ok"
31+
except Exception as db_error:
32+
logger.warning(f"Database health check failed: {str(db_error)}")
33+
health_status["checks"]["database"] = "failed"
34+
health_status["status"] = "degraded"
35+
36+
logger.info("Health check passed")
37+
status_code = 200 if health_status["status"] == "healthy" else 503
38+
39+
return JsonResponse(health_status, status=status_code)
40+
41+
except Exception as e:
42+
logger.error(f"Health check endpoint error: {str(e)}", exc_info=True)
43+
error_response = {
44+
"status": "unhealthy",
45+
"service": "aviso-core",
46+
"error": str(e)
47+
}
48+
return JsonResponse(error_response, status=503)

gbm_apis/urls.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
21
from django.urls import path
32
from gbm_apis.api.drilldown_fields_v2 import DrilldownFieldsV2
43
from gbm_apis.api.data_load import DataLoadAPIView
54
from gbm_apis.api.deals_results import DealsResultsAPIView
5+
from gbm_apis.api.health import HealthCheckView
66

77
app_name = 'gbm_apis'
88

99
urlpatterns = [
1010
path('v2/drilldown_fields/', DrilldownFieldsV2.as_view(), name='drilldown_fields_v2'),
1111
path('basic_results/', DataLoadAPIView.as_view(), name='basic_results'),
1212
path('deals_results/', DealsResultsAPIView.as_view(), name='deals_results'),
13-
]
13+
path('health/', HealthCheckView.as_view(), name='health'),
14+
]

0 commit comments

Comments
 (0)