Compare commits
26 commits
dfb4de6288
...
976d739b18
| Author | SHA1 | Date | |
|---|---|---|---|
| 976d739b18 | |||
| ce74860ca3 | |||
| 3224f8cfca | |||
| bc592a2c6c | |||
| 64726c56f3 | |||
| 88aa334846 | |||
| d5f94e26c6 | |||
| 53bbf001a0 | |||
| 239e51f0f8 | |||
| 7234560d16 | |||
| 8da4bc4aaf | |||
| 7fa243a8d4 | |||
| 9a8bc44e09 | |||
| 4b8ea49c0c | |||
| 08972266db | |||
| da1292a45f | |||
| a50df466d0 | |||
| 59c5844f5b | |||
| cd62e256cf | |||
| dcba20ee94 | |||
| 05931329a3 | |||
| 6ee6341a5e | |||
| 8f4dc486ac | |||
| 604381348a | |||
| 0e19df5c3e | |||
| e0bc04770b |
10 changed files with 92 additions and 9 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -14,6 +14,6 @@ uv.lock
|
||||||
**/*.kdbx*
|
**/*.kdbx*
|
||||||
.compose_repository
|
.compose_repository
|
||||||
|
|
||||||
__pycache__/
|
deployment/
|
||||||
**/dist/
|
**/dist/
|
||||||
**/*.log
|
**/*.log
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,7 @@ from starlette.responses import JSONResponse, Response
|
||||||
|
|
||||||
from karl.api.models import Request
|
from karl.api.models import Request
|
||||||
from karl.core.injects import AutowireSupport
|
from karl.core.injects import AutowireSupport
|
||||||
from karl.core.woodpecker import Woodpecker
|
from karl.model.webhook import WoodpeckerEvent, ReloadEvent
|
||||||
from karl.model.webhook import WoodpeckerEvent
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -21,7 +20,6 @@ async def root():
|
||||||
|
|
||||||
@cbv(router)
|
@cbv(router)
|
||||||
class APIv1:
|
class APIv1:
|
||||||
woodpecker: Woodpecker = Depends(AutowireSupport.woodpecker)
|
|
||||||
bus: EventBus = Depends(AutowireSupport.bus)
|
bus: EventBus = Depends(AutowireSupport.bus)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -39,3 +37,10 @@ class APIv1:
|
||||||
async def ci(self, request: Request):
|
async def ci(self, request: Request):
|
||||||
await self.bus.dispatch(mapper.map(request))
|
await self.bus.dispatch(mapper.map(request))
|
||||||
return Response(status_code=201)
|
return Response(status_code=201)
|
||||||
|
|
||||||
|
@router.get("/reload", summary="Manual service reload")
|
||||||
|
async def reload(self, service: str = None) -> Response:
|
||||||
|
if service is None:
|
||||||
|
return Response(status_code=400)
|
||||||
|
await self.bus.dispatch(ReloadEvent(service=service))
|
||||||
|
return Response(status_code=201)
|
||||||
|
|
|
||||||
|
|
@ -51,4 +51,8 @@ class Settings(BaseSettings):
|
||||||
|
|
||||||
@lru_cache
|
@lru_cache
|
||||||
def get_settings() -> Settings:
|
def get_settings() -> Settings:
|
||||||
return Settings.from_yaml()
|
paths = ['deployment/config.yaml', 'config/config.yaml']
|
||||||
|
for path in paths:
|
||||||
|
if Path(path).exists():
|
||||||
|
return Settings.from_yaml(path)
|
||||||
|
raise Exception("Config file not found")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from bubus import EventBus
|
from bubus import EventBus
|
||||||
from injectable import inject
|
from injectable import inject
|
||||||
|
|
||||||
|
from karl.core.reload import ReloadService
|
||||||
from karl.core.woodpecker import Woodpecker
|
from karl.core.woodpecker import Woodpecker
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -10,6 +11,10 @@ class AutowireSupport:
|
||||||
def woodpecker():
|
def woodpecker():
|
||||||
return inject(Woodpecker)
|
return inject(Woodpecker)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def reload():
|
||||||
|
return inject(ReloadService)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def bus():
|
def bus():
|
||||||
return inject(EventBus)
|
return inject(EventBus)
|
||||||
|
|
|
||||||
48
src/karl/core/reload.py
Normal file
48
src/karl/core/reload.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from bubus import EventBus
|
||||||
|
from injectable import injectable, autowired, Autowired
|
||||||
|
|
||||||
|
from karl import get_settings
|
||||||
|
from model.webhook import ReloadEvent, WoodpeckerEvent
|
||||||
|
from services import GitService
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@injectable(singleton=True)
|
||||||
|
class ReloadService:
|
||||||
|
|
||||||
|
@autowired
|
||||||
|
def __init__(self, bus: Annotated[EventBus, Autowired]):
|
||||||
|
self._bus = bus
|
||||||
|
self._git = GitService()
|
||||||
|
bus.on(ReloadEvent, self.on_reload)
|
||||||
|
self.root_path = get_settings().git.path
|
||||||
|
logger.info("ReloadService initialized.")
|
||||||
|
|
||||||
|
async def on_reload(self, event: ReloadEvent):
|
||||||
|
try:
|
||||||
|
logger.info(f"Received ReloadEvent: {event.service}")
|
||||||
|
head = self._git.get_head()
|
||||||
|
file_path = Path(self.root_path) / f"files/{event.service}"
|
||||||
|
if not file_path.exists():
|
||||||
|
raise Exception(f"Service {event.service} not found: {file_path.absolute()} does not exist.")
|
||||||
|
logger.debug(f"Found service files at {file_path}: {', '.join([str(f) for f in list(file_path.iterdir())])}")
|
||||||
|
mos = list(file_path.glob('*.mo.*'))
|
||||||
|
logger.debug(f"Found {len(mos)} .mo files")
|
||||||
|
we = WoodpeckerEvent(
|
||||||
|
_id=-1,
|
||||||
|
commit=head.sha,
|
||||||
|
ref=head.branch,
|
||||||
|
message=f"Manual reload of {event.service}",
|
||||||
|
started=int(datetime.now().timestamp()),
|
||||||
|
files=[f"compose/{event.service}/docker-compose.yml"] + [str(pp).replace(str(self.root_path), '') for pp in mos]
|
||||||
|
)
|
||||||
|
logger.debug(f"Sending <WoodpeckerEvent commit={we.commit} ref={we.ref} message={we.message} started={we.started} files={we.files}")
|
||||||
|
await self._bus.dispatch(we)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Reload error: {e}", exc_info=True)
|
||||||
|
|
@ -3,6 +3,7 @@ import logging
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from injectable import load_injection_container
|
from injectable import load_injection_container
|
||||||
|
|
||||||
|
from core.injects import AutowireSupport
|
||||||
from karl.config import get_settings
|
from karl.config import get_settings
|
||||||
from karl.util.logging import HandlerFactory
|
from karl.util.logging import HandlerFactory
|
||||||
|
|
||||||
|
|
@ -15,7 +16,7 @@ class KarlApplication:
|
||||||
_app = FastAPI(title="Karl", version="0.1.0")
|
_app = FastAPI(title="Karl", version="0.1.0")
|
||||||
self._set_middlewares(_app)
|
self._set_middlewares(_app)
|
||||||
self._set_routes(_app)
|
self._set_routes(_app)
|
||||||
self._set_events(_app)
|
self._init_services(_app)
|
||||||
|
|
||||||
self._app = _app
|
self._app = _app
|
||||||
|
|
||||||
|
|
@ -55,8 +56,9 @@ class KarlApplication:
|
||||||
app.include_router(api_v1_router, prefix="/api/v1", tags=["v1"])
|
app.include_router(api_v1_router, prefix="/api/v1", tags=["v1"])
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _set_events(self, app: FastAPI):
|
def _init_services(self, app: FastAPI):
|
||||||
pass
|
AutowireSupport.reload()
|
||||||
|
AutowireSupport.woodpecker()
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
|
|
|
||||||
7
src/karl/model/vcs.py
Normal file
7
src/karl/model/vcs.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Head:
|
||||||
|
sha: str
|
||||||
|
branch: str
|
||||||
|
|
@ -10,3 +10,6 @@ class WoodpeckerEvent(BaseEvent):
|
||||||
message: str
|
message: str
|
||||||
started: int
|
started: int
|
||||||
files: List[str]
|
files: List[str]
|
||||||
|
|
||||||
|
class ReloadEvent(BaseEvent):
|
||||||
|
service: str
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,4 @@ class DockerService:
|
||||||
os.chdir(compose_path.parent)
|
os.chdir(compose_path.parent)
|
||||||
self._client.compose.ps()
|
self._client.compose.ps()
|
||||||
self._client.compose.down(remove_orphans=True)
|
self._client.compose.down(remove_orphans=True)
|
||||||
self._client.compose.up()
|
self._client.compose.up(detach=True)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from git import Repo, Remote
|
||||||
from injectable import injectable
|
from injectable import injectable
|
||||||
|
|
||||||
from karl.config import GitConfig, get_settings
|
from karl.config import GitConfig, get_settings
|
||||||
|
from model.vcs import Head
|
||||||
|
|
||||||
|
|
||||||
@injectable(singleton=True)
|
@injectable(singleton=True)
|
||||||
|
|
@ -27,3 +28,11 @@ class GitService:
|
||||||
def checkout(self, sha: str):
|
def checkout(self, sha: str):
|
||||||
self._origin.fetch()
|
self._origin.fetch()
|
||||||
self._repo.git.checkout(sha)
|
self._repo.git.checkout(sha)
|
||||||
|
|
||||||
|
def get_head(self) -> Head:
|
||||||
|
if self._repo.head.is_detached:
|
||||||
|
return Head(self._repo.head.object.hexsha, "detached")
|
||||||
|
return Head(
|
||||||
|
self._repo.active_branch.commit.hexsha,
|
||||||
|
self._repo.active_branch.name
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue