Skip to content

Commit 56756ee

Browse files
committed
Verify azure auth
1 parent 87aae15 commit 56756ee

4 files changed

Lines changed: 739 additions & 52 deletions

File tree

Dockerfile

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,22 @@ FROM python:3.11-slim
22

33
WORKDIR /app
44

5-
# System deps (minimal)
65
RUN apt-get update && apt-get install -y --no-install-recommends \
76
curl \
87
&& rm -rf /var/lib/apt/lists/*
98

10-
# Install Python deps first (better layer caching)
119
COPY requirements.txt .
1210
RUN pip install --no-cache-dir -r requirements.txt
1311

14-
# Copy app code
1512
COPY . .
1613

17-
# Non-root user (security best practice)
1814
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
1915
USER appuser
2016

2117
EXPOSE 8000
2218

23-
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
24-
CMD curl -f http://localhost:8000/health || exit 1
19+
# Health check now includes auth verification via /ready
20+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
21+
CMD curl -fsS http://localhost:8000/ready || exit 1
2522

26-
CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000"]
23+
CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000"]

api.py

Lines changed: 110 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,145 @@
1-
"""FastAPI service with live config refresh."""
2-
import time
1+
"""FastAPI service with startup auth verification."""
2+
import sys
33
import logging
44
from contextlib import asynccontextmanager
55

66
from fastapi import FastAPI, HTTPException
7-
from pydantic import BaseModel, Field
87

9-
from agent import get_agent
8+
from config import get_settings, verify_azure_auth, get_auth_report
109
from cache import get_cache
11-
from config import get_settings, refresh_settings, get_app_config_loader
1210

1311

1412
@asynccontextmanager
1513
async def lifespan(app: FastAPI):
16-
settings = get_settings()
14+
# ===== Phase 1: Logging =====
1715
logging.basicConfig(
18-
level=settings.log_level,
16+
level="INFO", # Will be overridden once config is loaded
1917
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
2018
)
2119
logger = logging.getLogger(__name__)
22-
logger.info(f"Starting weather-agent (model={settings.openai_model})")
23-
logger.info(f"App Config enabled: {settings.use_app_configuration}")
24-
logger.info(f"Feature flags: streaming={settings.feature_streaming}, "
25-
f"response_cache={settings.feature_response_cache}")
2620

27-
get_cache()
28-
get_agent()
29-
logger.info("Agent ready ✓")
30-
yield
21+
logger.info("=" * 60)
22+
logger.info("🌤️ Weather Agent starting up")
23+
logger.info("=" * 60)
24+
25+
# ===== Phase 2: Verify Azure auth (fail fast) =====
26+
try:
27+
report = verify_azure_auth(strict=True)
28+
if not report.overall_success:
29+
# This branch only hits when strict=False; with strict=True
30+
# an exception will have been raised.
31+
logger.error("Azure auth verification FAILED")
32+
sys.exit(1)
33+
except RuntimeError as e:
34+
logger.error(f"❌ Startup failed: {e}")
35+
# Exit with non-zero so K8s restarts the pod (likely with backoff)
36+
sys.exit(1)
37+
except Exception as e:
38+
logger.exception(f"❌ Unexpected startup error: {e}")
39+
sys.exit(1)
40+
41+
# ===== Phase 3: Load config =====
42+
try:
43+
settings = get_settings()
44+
logging.getLogger().setLevel(settings.log_level)
45+
logger.info(f"Config loaded (model={settings.openai_model})")
46+
except Exception as e:
47+
logger.exception(f"❌ Config load failed: {e}")
48+
sys.exit(1)
49+
50+
# ===== Phase 4: Warm up dependencies =====
51+
try:
52+
get_cache()
53+
from agent import get_agent
54+
get_agent()
55+
logger.info("✅ Agent ready — accepting traffic")
56+
except Exception as e:
57+
logger.exception(f"❌ Agent warm-up failed: {e}")
58+
sys.exit(1)
59+
60+
yield # ← App is running
61+
62+
logger.info("👋 Shutting down")
3163

3264

33-
app = FastAPI(title="Weather Agent API", version="1.3.0", lifespan=lifespan)
65+
app = FastAPI(title="Weather Agent API", version="1.4.0", lifespan=lifespan)
3466
logger = logging.getLogger(__name__)
3567

3668

37-
class WeatherQuery(BaseModel):
38-
query: str = Field(..., min_length=1, max_length=500)
39-
bypass_cache: bool = False
40-
41-
42-
class WeatherResponse(BaseModel):
43-
answer: str
44-
latency_ms: int
45-
cached: bool = False
46-
model_used: str
47-
48-
49-
@app.get("/")
50-
def root():
51-
return {"service": "weather-agent", "status": "ok"}
52-
69+
# ===== Health & readiness endpoints =====
5370

5471
@app.get("/health")
5572
def health():
73+
"""Liveness probe — process is alive."""
5674
return {"status": "healthy"}
5775

5876

5977
@app.get("/ready")
6078
def ready():
79+
"""Readiness probe — pod can serve traffic.
80+
81+
Returns 503 if Azure auth checks haven't passed.
82+
"""
83+
report = get_auth_report()
84+
if report is None or not report.overall_success:
85+
raise HTTPException(
86+
status_code=503,
87+
detail={
88+
"status": "not_ready",
89+
"reason": "Azure authentication not verified",
90+
"failed_checks": [
91+
{"name": c.name, "error": c.error}
92+
for c in (report.failed_checks if report else [])
93+
],
94+
},
95+
)
96+
97+
settings = get_settings()
98+
return {
99+
"status": "ready",
100+
"model": settings.openai_model,
101+
"azure": {
102+
"app_config": settings.use_app_configuration,
103+
"key_vault": settings.use_key_vault,
104+
"identity": report.identity_info,
105+
},
106+
}
107+
108+
109+
@app.get("/auth/status")
110+
def auth_status():
111+
"""Show the Azure authentication verification report."""
112+
report = get_auth_report()
113+
if report is None:
114+
return {"status": "not_yet_verified"}
115+
return {
116+
"overall_success": report.overall_success,
117+
"identity": report.identity_info,
118+
"checks": [
119+
{
120+
"name": c.name,
121+
"success": c.success,
122+
"duration_ms": c.duration_ms,
123+
"detail": c.detail,
124+
"error": c.error,
125+
}
126+
for c in report.checks
127+
],
128+
}
129+
130+
131+
@app.post("/auth/verify")
132+
def reverify_auth():
133+
"""Manually re-run the auth verification (useful after credential rotation)."""
61134
try:
62-
settings = get_settings()
135+
report = verify_azure_auth(strict=False)
63136
return {
64-
"status": "ready",
65-
"model": settings.openai_model,
66-
"cache_backend": settings.cache_backend,
67-
"app_config": settings.use_app_configuration,
68-
"features": {
69-
"response_cache": settings.feature_response_cache,
70-
"tool_cache": settings.feature_tool_cache,
71-
"streaming": settings.feature_streaming,
72-
"strict_mode": settings.feature_strict_mode,
73-
},
137+
"overall_success": report.overall_success,
138+
"checks_passed": len([c for c in report.checks if c.success]),
139+
"checks_total": len(report.checks),
74140
}
75141
except Exception as e:
76-
raise HTTPException(status_code=503, detail=str(e))
77-
142+
raise HTTPException(status_code=500, detail=str(e))
78143

79144
@app.post("/ask", response_model=WeatherResponse)
80145
def ask_weather(payload: WeatherQuery):

0 commit comments

Comments
 (0)