-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 🚀 switch to uv * 🚨 fix linter errors
- Loading branch information
Showing
29 changed files
with
664 additions
and
261 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
.docker | ||
.git/ | ||
/.venv/ | ||
/venv/ | ||
/.idea/ | ||
**/__pycache__/ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
.venv/* | ||
venv/* | ||
.idea/* | ||
__pycache__ | ||
*.egg-info/* | ||
.coverage | ||
.pytest_cache/ | ||
build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,33 @@ | ||
FROM registry.access.redhat.com/ubi9/python-311:1-77@sha256:3231676407c7e727cfbd853137cfaab2f891410762268fbebe4ea9f27d8f568b as builder | ||
FROM registry.access.redhat.com/ubi9/python-311:1-77@sha256:3231676407c7e727cfbd853137cfaab2f891410762268fbebe4ea9f27d8f568b AS builder | ||
COPY --from=ghcr.io/astral-sh/uv:0.5.5@sha256:dc60491f42c9c7228fe2463f551af49a619ebcc9cbd10a470ced7ada63aa25d4 /uv /bin/uv | ||
WORKDIR /ghmirror | ||
RUN python3 -m venv venv | ||
ENV VIRTUAL_ENV=/ghmirror/venv | ||
ENV PATH="$VIRTUAL_ENV/bin:$PATH" | ||
COPY --chown=1001:0 setup.py VERSION ./ | ||
RUN pip install . | ||
|
||
FROM builder as test | ||
COPY --chown=1001:0 requirements-check.txt ./ | ||
RUN pip install -r requirements-check.txt | ||
COPY --chown=1001:0 . ./ | ||
ENTRYPOINT ["make"] | ||
CMD ["check"] | ||
COPY --chown=1001:0 pyproject.toml uv.lock ./ | ||
RUN uv lock --locked | ||
COPY --chown=1001:0 ghmirror ./ghmirror | ||
RUN uv sync --frozen --no-cache --compile-bytecode --no-group dev --python /usr/bin/python3.11 | ||
|
||
FROM registry.access.redhat.com/ubi9/ubi-minimal:9.4-1227@sha256:f182b500ff167918ca1010595311cf162464f3aa1cab755383d38be61b4d30aa | ||
FROM registry.access.redhat.com/ubi9/ubi-minimal:9.4-1227@sha256:f182b500ff167918ca1010595311cf162464f3aa1cab755383d38be61b4d30aa AS prod | ||
RUN microdnf upgrade -y && \ | ||
microdnf install -y python3.11 && \ | ||
microdnf clean all | ||
COPY LICENSE /licenses/LICENSE | ||
USER 1001 | ||
WORKDIR /ghmirror | ||
ENV VIRTUAL_ENV=/ghmirror/venv | ||
RUN chown -R 1001:0 /ghmirror | ||
USER 1001 | ||
ENV VIRTUAL_ENV=/ghmirror/.venv | ||
ENV PATH="$VIRTUAL_ENV/bin:$PATH" | ||
COPY --from=builder $VIRTUAL_ENV $VIRTUAL_ENV | ||
COPY --chown=1001:0 . ./ | ||
COPY --from=builder /ghmirror /ghmirror | ||
ENTRYPOINT ["gunicorn", "ghmirror.app:APP"] | ||
CMD ["--workers", "1", "--threads", "8", "--bind", "0.0.0.0:8080"] | ||
|
||
FROM prod AS test | ||
COPY --from=ghcr.io/astral-sh/uv:0.5.5@sha256:dc60491f42c9c7228fe2463f551af49a619ebcc9cbd10a470ced7ada63aa25d4 /uv /bin/uv | ||
USER root | ||
RUN microdnf install -y make | ||
USER 1001 | ||
COPY --chown=1001:0 Makefile ./ | ||
COPY --chown=1001:0 tests ./tests | ||
ENV UV_NO_CACHE=true | ||
RUN uv sync --frozen | ||
RUN make check | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,11 @@ | ||
develop: | ||
pip install --editable . | ||
pip install -r requirements-check.txt | ||
|
||
|
||
check: | ||
ruff check --no-fix | ||
ruff format --check | ||
python3 -m pytest -v --forked --cov=ghmirror --cov-report=term-missing tests/ | ||
uv run ruff check --no-fix | ||
uv run ruff format --check | ||
uv run pytest -v --forked --cov=ghmirror --cov-report=term-missing tests/ | ||
|
||
accept: | ||
python3 acceptance/test_basic.py | ||
|
||
format: | ||
ruff check | ||
ruff format | ||
uv run ruff check | ||
uv run ruff format |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,9 +12,7 @@ | |
# Copyright: Red Hat Inc. 2020 | ||
# Author: Amador Pahim <[email protected]> | ||
|
||
""" | ||
The GitHub Mirror endpoints | ||
""" | ||
"""The GitHub Mirror endpoints""" | ||
|
||
import logging | ||
import os | ||
|
@@ -37,12 +35,10 @@ | |
|
||
|
||
def error_handler(exception): | ||
""" | ||
Used when an exception happens in the flask app. | ||
""" | ||
"""Used when an exception happens in the flask app.""" | ||
return ( | ||
flask.jsonify( | ||
message=f"Error reaching {GH_API}: {str(exception.__class__.__name__)}" | ||
message=f"Error reaching {GH_API}: {exception.__class__.__name__!s}" | ||
), | ||
502, | ||
) | ||
|
@@ -54,17 +50,13 @@ def error_handler(exception): | |
|
||
@APP.route("/healthz", methods=["GET"]) | ||
def healthz(): | ||
""" | ||
Health check endpoint for Kubernetes. | ||
""" | ||
"""Health check endpoint for Kubernetes.""" | ||
return flask.Response("OK") | ||
|
||
|
||
@APP.route("/metrics", methods=["GET"]) | ||
def metrics(): | ||
""" | ||
Prometheus metrics endpoint. | ||
""" | ||
"""Prometheus metrics endpoint.""" | ||
headers = {"Content-type": "text/plain"} | ||
|
||
stats_cache = StatsCache() | ||
|
@@ -80,9 +72,7 @@ def metrics(): | |
@APP.route("/<path:path>", methods=["GET", "POST", "PUT", "PATCH", "DELETE"]) | ||
@check_user | ||
def ghmirror(path): | ||
""" | ||
Default endpoint, matching any url without a specific endpoint. | ||
""" | ||
"""Default endpoint, matching any url without a specific endpoint.""" | ||
url = f"{GH_API}/{path}" | ||
|
||
if flask.request.args: | ||
|
@@ -111,4 +101,8 @@ def ghmirror(path): | |
|
||
|
||
if __name__ == "__main__": # pragma: no cover | ||
APP.run(host="127.0.0.1", debug=True, port="8080") | ||
APP.run( | ||
host="127.0.0.1", | ||
debug=bool(os.environ.get("GITHUB_MIRROR_DEBUG", "1")), | ||
port=8080, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,9 +12,7 @@ | |
# Copyright: Red Hat Inc. 2020 | ||
# Author: Amador Pahim <[email protected]> | ||
|
||
""" | ||
System constants. | ||
""" | ||
"""System constants.""" | ||
|
||
GH_API = "https://api.github.com" | ||
GH_STATUS_API = "https://www.githubstatus.com/api/v2/components.json" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,9 +12,7 @@ | |
# Copyright: Red Hat Inc. 2020 | ||
# Author: Amador Pahim <[email protected]> | ||
|
||
""" | ||
Implements conditional requests | ||
""" | ||
"""Implements conditional requests""" | ||
|
||
# ruff: noqa: PLR2004 | ||
import hashlib | ||
|
@@ -35,10 +33,7 @@ | |
|
||
|
||
def _get_elements_per_page(url_params): | ||
""" | ||
Get 'per_page' parameter if present in URL or | ||
return None if not present | ||
""" | ||
"""Get 'per_page' parameter if present in URL or return None if not present""" | ||
if url_params is not None: | ||
per_page = url_params.get("per_page") | ||
if per_page is not None: | ||
|
@@ -48,10 +43,10 @@ def _get_elements_per_page(url_params): | |
|
||
|
||
def _cache_response(resp, cache, cache_key): | ||
""" | ||
Implements the logic to decide whether or not | ||
whe should cache a request acording to the headers | ||
and content | ||
"""Cache response if it makes sense | ||
Implements the logic to decide whether or not whe should cache a request acording | ||
to the headers and content | ||
""" | ||
# Caching only makes sense when at least one | ||
# of those headers is present | ||
|
@@ -65,10 +60,7 @@ def _cache_response(resp, cache, cache_key): | |
def _online_request( | ||
session, method, url, cached_response, headers=None, parameters=None | ||
): | ||
""" | ||
Handle API errors on conditional requests and try | ||
to serve contents from cache | ||
""" | ||
"""Handle API errors on conditional requests and try to serve contents from cache""" | ||
try: | ||
resp = session.request( | ||
method=method, | ||
|
@@ -146,21 +138,18 @@ def _handle_not_changed( | |
|
||
@requests_metrics | ||
def conditional_request(session, method, url, auth, data=None, url_params=None): | ||
""" | ||
Implements conditional requests, checking first whether | ||
the upstream API is online of offline to decide which | ||
"""Implements conditional requests. | ||
Checking first whether the upstream API is online of offline to decide which | ||
request routine to call. | ||
""" | ||
if GithubStatus().online: | ||
return online_request(session, method, url, auth, data, url_params) | ||
return offline_request(method, url, auth) | ||
|
||
|
||
# pylint: disable-msg=too-many-locals | ||
def online_request(session, method, url, auth, data=None, url_params=None): | ||
""" | ||
Implements conditional requests. | ||
""" | ||
"""Implements conditional requests.""" | ||
cache = RequestsCache() | ||
headers = {} | ||
parameters = url_params.to_dict() if url_params is not None else {} | ||
|
@@ -241,8 +230,7 @@ def online_request(session, method, url, auth, data=None, url_params=None): | |
|
||
|
||
def _should_error_response_be_served_from_cache(response): | ||
"""Parse a response to check if we should serve contents | ||
from cache | ||
"""Parse a response to check if we should serve contents from cache | ||
:param response: requests module response | ||
:type response: requests.Response | ||
|
@@ -251,7 +239,6 @@ def _should_error_response_be_served_from_cache(response): | |
from cache | ||
:rtype: str, optional | ||
""" | ||
|
||
if _is_rate_limit_error(response): | ||
return "RATE_LIMITED" | ||
|
||
|
@@ -280,9 +267,7 @@ def _is_rate_limit_error(response): | |
def offline_request( | ||
method, url, auth, error_code=504, error_message=b'{"message": "gateway timeout"}\n' | ||
): | ||
""" | ||
Implements offline requests (serves content from cache, when possible). | ||
""" | ||
"""Implements offline requests (serves content from cache, when possible).""" | ||
headers = {} | ||
if auth is None: | ||
auth_sha = None | ||
|
@@ -299,8 +284,7 @@ def offline_request( | |
response = requests.models.Response() | ||
response.status_code = error_code | ||
response.headers["X-Cache"] = "OFFLINE_MISS" | ||
# pylint: disable=protected-access | ||
response._content = error_message | ||
response._content = error_message # noqa: SLF001 | ||
return response | ||
|
||
cache = RequestsCache() | ||
|
@@ -320,6 +304,5 @@ def offline_request( | |
response = requests.models.Response() | ||
response.status_code = error_code | ||
response.headers["X-Cache"] = "OFFLINE_MISS" | ||
# pylint: disable=protected-access | ||
response._content = error_message | ||
response._content = error_message # noqa: SLF001 | ||
return response |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,16 +12,14 @@ | |
# Copyright: Red Hat Inc. 2020 | ||
# Author: Amador Pahim <[email protected]> | ||
|
||
""" | ||
Module containing all the abstractions around an HTTP response. | ||
""" | ||
"""Module containing all the abstractions around an HTTP response.""" | ||
|
||
|
||
class MirrorResponse: | ||
""" | ||
Wrapper around the requests.Response, implementing properties | ||
that replace the strings containing the GutHub API url by the | ||
mirror url where needed. | ||
"""Wrapper around the requests.Response. | ||
Implementing properties that replace the strings containing the | ||
GutHub API url by the mirror url where needed. | ||
:param original_response: the return from the original request | ||
to the GitHub API | ||
|
@@ -40,10 +38,10 @@ def __init__(self, original_response, gh_api_url, gh_mirror_url): | |
|
||
@property | ||
def headers(self): | ||
""" | ||
Retrieves the headers we are interested in from the original | ||
response and sanitizes them so we can impersonate the GitHub | ||
API. | ||
"""Sanitize headers. | ||
Retrieves the headers we are interested in from the original response and | ||
sanitizes them so we can impersonate the GitHub API. | ||
:return: the sanitized headers | ||
:rtype: dict | ||
|
@@ -76,7 +74,8 @@ def headers(self): | |
|
||
@property | ||
def content(self): | ||
""" | ||
"""Sanitize content. | ||
Retrieves the content from the original response and sanitizes | ||
them so we can impersonate the GitHub API. | ||
|
@@ -86,17 +85,13 @@ def content(self): | |
if self._original_response.content is None: | ||
return None | ||
|
||
sanitized_content = self._original_response.content.replace( | ||
return self._original_response.content.replace( | ||
self._gh_api_url.encode(), self._gh_mirror_url.encode() | ||
) | ||
|
||
return sanitized_content | ||
|
||
@property | ||
def status_code(self): | ||
""" | ||
Convenience method to expose the original response HTTP | ||
status code. | ||
"""Convenience method to expose the original response HTTP status code. | ||
:return: the response status code | ||
""" | ||
|
Oops, something went wrong.