Skip to content

Commit 17731aa

Browse files
authored
Use updates.codegate.ai for /versions instead of GitHub (#1264)
Wire up CodeGate to use the new update service to determine the latest version. This is kept behind a config flag, but I have tested locally.
1 parent dfe4ef5 commit 17731aa

File tree

7 files changed

+94
-10
lines changed

7 files changed

+94
-10
lines changed

migrations/versions/2025_03_05_2126-e4c05d7591a8_add_installation_table.py

-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
from typing import Sequence, Union
1010

1111
from alembic import op
12-
import sqlalchemy as sa
13-
1412

1513
# revision identifiers, used by Alembic.
1614
revision: str = "e4c05d7591a8"

poetry.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/codegate/api/v1.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from codegate.config import API_DEFAULT_PAGE_SIZE, API_MAX_PAGE_SIZE
1212
import codegate.muxing.models as mux_models
13-
from codegate import __version__
13+
from codegate import Config, __version__
1414
from codegate.api import v1_models, v1_processing
1515
from codegate.db.connection import AlreadyExistsError, DbReader
1616
from codegate.db.models import AlertSeverity, AlertTriggerType, Persona, WorkspaceWithModel
@@ -20,6 +20,7 @@
2020
PersonaSimilarDescriptionError,
2121
)
2222
from codegate.providers import crud as provendcrud
23+
from codegate.updates.client import Origin, UpdateClient
2324
from codegate.workspaces import crud
2425

2526
logger = structlog.get_logger("codegate")
@@ -31,6 +32,7 @@
3132

3233
# This is a singleton object
3334
dbreader = DbReader()
35+
update_client = UpdateClient(Config.get_config().update_service_url, __version__, dbreader)
3436

3537

3638
def uniq_name(route: APIRoute):
@@ -724,10 +726,12 @@ async def stream_sse():
724726

725727

726728
@v1.get("/version", tags=["Dashboard"], generate_unique_id_function=uniq_name)
727-
def version_check():
729+
async def version_check():
728730
try:
729-
latest_version = v1_processing.fetch_latest_version()
730-
731+
if Config.get_config().use_update_service:
732+
latest_version = await update_client.get_latest_version(Origin.FrontEnd)
733+
else:
734+
latest_version = v1_processing.fetch_latest_version()
731735
# normalize the versions as github will return them with a 'v' prefix
732736
current_version = __version__.lstrip("v")
733737
latest_version_stripped = latest_version.lstrip("v")

src/codegate/cli.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
from codegate.config import Config, ConfigurationError
1717
from codegate.db.connection import (
1818
init_db_sync,
19-
init_session_if_not_exists,
2019
init_instance,
20+
init_session_if_not_exists,
2121
)
2222
from codegate.pipeline.factory import PipelineFactory
2323
from codegate.pipeline.sensitive_data.manager import SensitiveDataManager

src/codegate/config.py

+29-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ class Config:
5959
server_key: str = "server.key"
6060
force_certs: bool = False
6161

62+
# Update configuration.
63+
use_update_service: bool = False
64+
update_service_url: str = "https://updates.codegate.ai/api/v1/version"
65+
6266
max_fim_hash_lifetime: int = 60 * 5 # Time in seconds. Default is 5 minutes.
6367

6468
# Min value is 0 (max similarity), max value is 2 (orthogonal)
@@ -165,6 +169,8 @@ def from_file(cls, config_path: Union[str, Path]) -> "Config":
165169
force_certs=config_data.get("force_certs", cls.force_certs),
166170
prompts=prompts_config,
167171
provider_urls=provider_urls,
172+
use_update_service=config_data.get("use_update_service", cls.use_update_service),
173+
update_service_url=config_data.get("update_service_url", cls.update_service_url),
168174
)
169175
except yaml.YAMLError as e:
170176
raise ConfigurationError(f"Failed to parse config file: {e}")
@@ -209,11 +215,17 @@ def from_env(cls) -> "Config":
209215
if "CODEGATE_SERVER_KEY" in os.environ:
210216
config.server_key = os.environ["CODEGATE_SERVER_KEY"]
211217
if "CODEGATE_FORCE_CERTS" in os.environ:
212-
config.force_certs = os.environ["CODEGATE_FORCE_CERTS"]
218+
config.force_certs = cls.__bool_from_string(os.environ["CODEGATE_FORCE_CERTS"])
213219
if "CODEGATE_DB_PATH" in os.environ:
214220
config.db_path = os.environ["CODEGATE_DB_PATH"]
215221
if "CODEGATE_VEC_DB_PATH" in os.environ:
216222
config.vec_db_path = os.environ["CODEGATE_VEC_DB_PATH"]
223+
if "CODEGATE_USE_UPDATE_SERVICE" in os.environ:
224+
config.use_update_service = cls.__bool_from_string(
225+
os.environ["CODEGATE_USE_UPDATE_SERVICE"]
226+
)
227+
if "CODEGATE_UPDATE_SERVICE_URL" in os.environ:
228+
config.update_service_url = os.environ["CODEGATE_UPDATE_SERVICE_URL"]
217229

218230
# Load provider URLs from environment variables
219231
for provider in DEFAULT_PROVIDER_URLS.keys():
@@ -246,6 +258,8 @@ def load(
246258
force_certs: Optional[bool] = None,
247259
db_path: Optional[str] = None,
248260
vec_db_path: Optional[str] = None,
261+
use_update_service: Optional[bool] = None,
262+
update_service_url: Optional[str] = None,
249263
) -> "Config":
250264
"""Load configuration with priority resolution.
251265
@@ -274,6 +288,8 @@ def load(
274288
force_certs: Optional flag to force certificate generation
275289
db_path: Optional path to the main SQLite database file
276290
vec_db_path: Optional path to the vector SQLite database file
291+
use_update_service: Optional flag to enable the update service
292+
update_service_url: Optional URL for the update service
277293
278294
Returns:
279295
Config: Resolved configuration
@@ -326,6 +342,10 @@ def load(
326342
config.db_path = env_config.db_path
327343
if "CODEGATE_VEC_DB_PATH" in os.environ:
328344
config.vec_db_path = env_config.vec_db_path
345+
if "CODEGATE_USE_UPDATE_SERVICE" in os.environ:
346+
config.use_update_service = env_config.use_update_service
347+
if "CODEGATE_UPDATE_SERVICE_URL" in os.environ:
348+
config.update_service_url = env_config.update_service_url
329349

330350
# Override provider URLs from environment
331351
for provider, url in env_config.provider_urls.items():
@@ -366,6 +386,10 @@ def load(
366386
config.vec_db_path = vec_db_path
367387
if force_certs is not None:
368388
config.force_certs = force_certs
389+
if use_update_service is not None:
390+
config.use_update_service = use_update_service
391+
if update_service_url is not None:
392+
config.update_service_url = update_service_url
369393

370394
# Set the __config class attribute
371395
Config.__config = config
@@ -375,3 +399,7 @@ def load(
375399
@classmethod
376400
def get_config(cls) -> "Config":
377401
return cls.__config
402+
403+
@staticmethod
404+
def __bool_from_string(raw_value) -> bool:
405+
return raw_value.lower() == "true"

src/codegate/db/connection.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ async def init_instance(self) -> None:
619619
await self._execute_with_no_return(sql, instance.model_dump())
620620
except IntegrityError as e:
621621
logger.debug(f"Exception type: {type(e)}")
622-
raise AlreadyExistsError(f"Instance already initialized.")
622+
raise AlreadyExistsError("Instance already initialized.")
623623

624624

625625
class DbReader(DbCodeGate):

src/codegate/updates/client.py

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from enum import Enum
2+
3+
import cachetools.func
4+
import requests
5+
import structlog
6+
7+
from codegate.db.connection import DbReader
8+
9+
logger = structlog.get_logger("codegate")
10+
11+
12+
# Enum representing whether the request is coming from the front-end or the back-end.
13+
class Origin(Enum):
14+
FrontEnd = "FE"
15+
BackEnd = "BE"
16+
17+
18+
class UpdateClient:
19+
def __init__(self, update_url: str, current_version: str, db_reader: DbReader):
20+
self.__update_url = update_url
21+
self.__current_version = current_version
22+
self.__db_reader = db_reader
23+
self.__instance_id = None
24+
25+
async def get_latest_version(self, origin: Origin) -> str:
26+
"""
27+
Retrieves the latest version of CodeGate from updates.codegate.ai
28+
"""
29+
logger.info(f"Fetching latest version from {self.__update_url}")
30+
instance_id = await self.__get_instance_id()
31+
return self.__fetch_latest_version(instance_id, origin)
32+
33+
@cachetools.func.ttl_cache(maxsize=128, ttl=20 * 60)
34+
def __fetch_latest_version(self, instance_id: str, origin: Origin) -> str:
35+
headers = {
36+
"X-Instance-ID": instance_id,
37+
"User-Agent": f"codegate/{self.__current_version} {origin.value}",
38+
}
39+
40+
try:
41+
response = requests.get(self.__update_url, headers=headers, timeout=10)
42+
# Throw if the request was not successful.
43+
response.raise_for_status()
44+
return response.json()["version"]
45+
except Exception as e:
46+
logger.error(f"Error fetching latest version from f{self.__update_url}: {e}")
47+
return "unknown"
48+
49+
# Lazy load the instance ID from the DB.
50+
async def __get_instance_id(self):
51+
if self.__instance_id is None:
52+
instance_data = await self.__db_reader.get_instance()
53+
self.__instance_id = instance_data[0].id
54+
return self.__instance_id

0 commit comments

Comments
 (0)