Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ RUN mkdir -p $BIN_DIR \
&& mkdir -p $DATA_DIR \
&& mkdir -p $DATA_DIR/logs

COPY gunicorn.conf.py pyproject.toml uv.lock README.md $APP_DIR/
COPY entrypoint.sh entrypoint.py pyproject.toml uv.lock README.md $APP_DIR/
COPY webhook_server $APP_DIR/webhook_server/
Comment thread
myakove marked this conversation as resolved.
RUN chmod +x $APP_DIR/entrypoint.sh

RUN usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USERNAME \
&& chown -R $USERNAME:$USERNAME $HOME_DIR
Expand Down Expand Up @@ -66,4 +67,4 @@ RUN uv sync

HEALTHCHECK CMD curl --fail http://127.0.0.1:5000/webhook_server/healthcheck || exit 1

ENTRYPOINT ["uv", "run", "gunicorn", "webhook_server.app:FASTAPI_APP", "-c", "./gunicorn.conf.py"]
ENTRYPOINT ["./entrypoint.sh"]
13 changes: 13 additions & 0 deletions entrypoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from webhook_server.libs.config import Config
from webhook_server.utils.github_repository_and_webhook_settings import repository_and_webhook_settings

_config = Config()
_root_config = _config.root_data
_ip_bind = _root_config.get("ip-bind", "0.0.0.0")
_port = _root_config.get("port", 5000)
_max_workers = _root_config.get("max-workers", 10)
_webhook_secret = _root_config.get("webhook-secret")

if __name__ == "__main__":
repository_and_webhook_settings(webhook_secret=_webhook_secret)
print(f"uv run uvicorn webhook_server.app:FASTAPI_APP --host {_ip_bind} --port {_port} --workers {_max_workers}")
9 changes: 9 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail

# Generate the uvicorn command from Python
cmd=$(uv run ./entrypoint.py)
echo "${cmd}"

# Replace the shell with the server process (PID 1 inside container)
exec ${cmd}
14 changes: 0 additions & 14 deletions gunicorn.conf.py

This file was deleted.

4 changes: 1 addition & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ dependencies = [
"string-color>=1.2.3",
"timeout-sampler>=0.0.46",
"uvicorn>=0.31.0",
"uvicorn-worker>=0.3.0",
"gunicorn>=23.0.0",
"httpx>=0.28.1"
"httpx>=0.28.1",
]

[[project.authors]]
Expand Down
29 changes: 0 additions & 29 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 26 additions & 22 deletions webhook_server/app.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import hashlib
import hmac
import ipaddress
import logging
import os
import sys
from typing import Any
from contextlib import asynccontextmanager
from typing import Any, AsyncGenerator

import requests
import urllib3
Expand All @@ -20,11 +22,10 @@
from webhook_server.libs.config import Config
from webhook_server.libs.exceptions import NoPullRequestError, RepositoryNotFoundError
from webhook_server.libs.github_api import GithubWebhook
from webhook_server.utils.github_repository_and_webhook_settings import repository_and_webhook_settings
from webhook_server.utils.helpers import get_logger_with_params

ALLOWED_IPS: tuple[ipaddress._BaseNetwork, ...] = ()
FASTAPI_APP: FastAPI = FastAPI(title="webhook-server")

APP_URL_ROOT_PATH: str = "/webhook_server"
urllib3.disable_warnings()

Expand Down Expand Up @@ -82,17 +83,16 @@ async def gate_by_allowlist_ips(request: Request) -> None:
)


def on_starting(server: Any) -> None:
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
logger = get_logger_with_params(name="startup")
logger.info("Application starting up...")

try:
logger.info("Application starting up...")
config = Config(logger=logger)
root_config = config.root_data
webhook_secret = root_config.get("webhook-secret")
verify_github_ips = root_config.get("verify-github-ips")
verify_cloudflare_ips = root_config.get("verify-cloudflare-ips")

repository_and_webhook_settings(webhook_secret=webhook_secret)
logger.info("Repository and webhook settings initialized successfully.")

global ALLOWED_IPS
Expand All @@ -110,11 +110,15 @@ def on_starting(server: Any) -> None:

logger.info(f"IP allowlist initialized successfully. {ALLOWED_IPS}")

yield
except Exception as ex:
logger.exception(f"FATAL: Error during startup initialization: {ex}")
logger.error(f"Application failed to start up: {ex}")
raise


FASTAPI_APP: FastAPI = FastAPI(title="webhook-server", lifespan=lifespan)


@FASTAPI_APP.get(f"{APP_URL_ROOT_PATH}/healthcheck")
def healthcheck() -> dict[str, Any]:
return {"status": requests.codes.ok, "message": "Alive"}
Expand Down Expand Up @@ -148,28 +152,28 @@ async def process_webhook(request: Request, background_tasks: BackgroundTasks) -

logger = get_logger_with_params(name=logger_name, repository_name=hook_data["repository"]["name"])

try:
api: GithubWebhook = GithubWebhook(hook_data=hook_data, headers=request.headers, logger=logger)
async def process_with_error_handling(_api: GithubWebhook, _logger: logging.Logger) -> None:
try:
await _api.process()

async def process_with_error_handling() -> None:
try:
await api.process()
except NoPullRequestError:
return

except NoPullRequestError:
return
except Exception as e:
_logger.exception(f"{log_context} Error in background task: {e}")

except Exception as e:
logger.exception(f"{log_context} Error in background task: {e}")
try:
api: GithubWebhook = GithubWebhook(hook_data=hook_data, headers=request.headers, logger=logger)

background_tasks.add_task(process_with_error_handling)
return {"status": requests.codes.ok, "message": "process success", "log_prefix": delivery_headers}
background_tasks.add_task(process_with_error_handling, _api=api, _logger=logger)
return {"status": requests.codes.ok, "message": "ok", "delivery headers": delivery_headers}

except RepositoryNotFoundError as e:
logger.error(f"{log_context} Configuration/Repository error: {e}")
logger.exception(f"{log_context} Configuration/Repository error: {e}")
raise HTTPException(status_code=404, detail=str(e))

except ConnectionError as e:
logger.error(f"{log_context} API connection error: {e}")
logger.exception(f"{log_context} API connection error: {e}")
raise HTTPException(status_code=503, detail=f"API Connection Error: {e}")

except NoPullRequestError as e:
Expand Down
12 changes: 6 additions & 6 deletions webhook_server/utils/github_repository_and_webhook_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@
from webhook_server.utils.helpers import get_api_with_highest_rate_limit, get_logger_with_params
from webhook_server.utils.webhook import create_webhook

LOGGER = get_logger_with_params(name="repository-and-webhook-settings")


def get_repository_api(repository: str) -> tuple[str, github.Github | None, str]:
config = Config(repository=repository)
config = Config(repository=repository, logger=LOGGER)
github_api, _, api_user = get_api_with_highest_rate_limit(config=config, repository_name=repository)
return repository, github_api, api_user


def repository_and_webhook_settings(webhook_secret: str | None = None) -> None:
logger = get_logger_with_params(name="github-repository-and-webhook-settings")

config = Config(logger=logger)
config = Config(logger=LOGGER)
apis_dict: dict[str, dict[str, Any]] = {}

apis: list = []
with ThreadPoolExecutor() as executor:
for repo, data in config.root_data["repositories"].items():
for repo, _ in config.root_data["repositories"].items():
apis.append(
executor.submit(
get_repository_api,
Expand All @@ -38,7 +38,7 @@ def repository_and_webhook_settings(webhook_secret: str | None = None) -> None:
repository, github_api, api_user = result.result()
apis_dict[repository] = {"api": github_api, "user": api_user}

logger.debug(f"Repositories APIs: {apis_dict}")
LOGGER.debug(f"Repositories APIs: {apis_dict}")

set_repositories_settings(config=config, apis_dict=apis_dict)
set_all_in_progress_check_runs_to_queued(repo_config=config, apis_dict=apis_dict)
Expand Down
Loading