Skip to content

Commit

Permalink
feat: version endpoint (#612)
Browse files Browse the repository at this point in the history
* feat: version endpoint

* chore: move version check into dashboard router, mock Github API call in tests

* chore: revert unecessary changes to server.py

* fix(versions endpoint): add logging, handle broader range of exceptions, don't return raw error detail to client

* refactor(version endpoint): use requests instead of httpx + ThreadPoolExecutor

* fix(version endpoint): add timeout to request to github API
  • Loading branch information
alex-mcgovern authored Jan 17, 2025
1 parent 234727a commit e4b3ceb
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 1 deletion.
47 changes: 46 additions & 1 deletion src/codegate/dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
import json
from typing import AsyncGenerator, List, Optional

import requests
import structlog
from fastapi import APIRouter, Depends, FastAPI
from fastapi.responses import StreamingResponse
from codegate import __version__

from codegate.dashboard.post_processing import (
parse_get_alert_conversation,
Expand All @@ -18,13 +20,22 @@
dashboard_router = APIRouter(tags=["Dashboard"])
db_reader = None


def get_db_reader():
global db_reader
if db_reader is None:
db_reader = DbReader()
return db_reader

def fetch_latest_version() -> str:
url = "https://api.github.com/repos/stacklok/codegate/releases/latest"
headers = {
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28"
}
response = requests.get(url, headers=headers, timeout=5)
response.raise_for_status()
data = response.json()
return data.get("tag_name", "unknown")

@dashboard_router.get("/dashboard/messages")
def get_messages(db_reader: DbReader = Depends(get_db_reader)) -> List[Conversation]:
Expand Down Expand Up @@ -61,6 +72,40 @@ async def stream_sse():
"""
return StreamingResponse(generate_sse_events(), media_type="text/event-stream")

@dashboard_router.get("/dashboard/version")
def version_check():
try:
latest_version = fetch_latest_version()

# normalize the versions as github will return them with a 'v' prefix
current_version = __version__.lstrip('v')
latest_version_stripped = latest_version.lstrip('v')

is_latest: bool = latest_version_stripped == current_version

return {
"current_version": current_version,
"latest_version": latest_version_stripped,
"is_latest": is_latest,
"error": None,
}
except requests.RequestException as e:
logger.error(f"RequestException: {str(e)}")
return {
"current_version": __version__,
"latest_version": "unknown",
"is_latest": None,
"error": "An error occurred while fetching the latest version"
}
except Exception as e:
logger.error(f"Unexpected error: {str(e)}")
return {
"current_version": __version__,
"latest_version": "unknown",
"is_latest": None,
"error": "An unexpected error occurred"
}


def generate_openapi():
# Create a temporary FastAPI app instance
Expand Down
12 changes: 12 additions & 0 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@ def test_health_check(test_client: TestClient) -> None:
assert response.status_code == 200
assert response.json() == {"status": "healthy"}

@patch("codegate.dashboard.dashboard.fetch_latest_version", return_value="foo")
def test_version_endpoint(mock_fetch_latest_version, test_client: TestClient) -> None:
"""Test the version endpoint."""
response = test_client.get("/dashboard/version")
assert response.status_code == 200

response_data = response.json()

assert response_data["current_version"] == __version__.lstrip('v')
assert response_data["latest_version"] == "foo"
assert isinstance(response_data["is_latest"], bool)
assert response_data["is_latest"] is False

@patch("codegate.pipeline.secrets.manager.SecretsManager")
@patch("codegate.server.ProviderRegistry")
Expand Down

0 comments on commit e4b3ceb

Please sign in to comment.