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
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.git
.github
.venv
venv
ENV
.env
.idea
__pycache__
*.pyc
.coverage
coverage.xml
htmlcov
tests
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM python:3.14-slim

ENV PYTHONUNBUFFERED=1

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY blurb_it blurb_it
COPY templates templates
COPY static static

CMD ["python", "-m", "blurb_it"]
18 changes: 14 additions & 4 deletions blurb_it/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ async def handle_install(request: Request) -> Response:
return response


@routes.get("/health", name="health")
async def health_check(request: Request) -> Response:
"""Health check endpoint for container orchestration."""
return web.Response(status=200, text="OK")


@routes.get("/add_blurb", name="add_blurb")
async def handle_add_blurb_get(request: Request) -> Response:
"""Render a page with a textbox and submit button."""
Expand Down Expand Up @@ -237,9 +243,13 @@ async def handle_add_blurb_post(request: Request) -> Response:
app, loader=jinja2.FileSystemLoader(os.path.join(os.getcwd(), "templates"))
)
app["static_root_url"] = os.path.join(os.getcwd(), "static")
port = os.environ.get("PORT")
if port is not None:
port = int(port)
app.router.add_routes(routes)
app.add_routes([web.static("/static", os.path.join(os.getcwd(), "static"))])
web.run_app(app, port=port)

if os.path.isdir("/var/run/cabotage"):
# On cabotage the TLS sidecar proxies to this unix socket.
os.umask(0)
web.run_app(app, path="/var/run/cabotage/cabotage.sock")
else:
port = os.environ.get("PORT")
web.run_app(app, port=int(port) if port else None)
18 changes: 14 additions & 4 deletions readme.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,20 @@ Uninstall blurb-it
Deploy
======

|Deploy|

.. |Deploy| image:: https://www.herokucdn.com/deploy/button.svg
:target: https://heroku.com/deploy?template=https://github.com/python/blurb_it
blurb-it is deployed to `cabotage <https://github.com/cabotage/cabotage-app>`_,
the container platform managed by the PSF. cabotage builds the image from the
``Dockerfile`` and runs the ``web`` process from the ``Procfile``. The ``web``
process listens on the unix socket ``/var/run/cabotage/cabotage.sock`` when
that directory exists, and on ``PORT`` otherwise. ``GET /health`` is the
health check path.

Configuration variables, set in the cabotage UI:

- ``GH_CLIENT_ID``, ``GH_CLIENT_SECRET``: the GitHub OAuth App credentials.
- ``GH_APP_ID``, ``GH_PRIVATE_KEY``: the GitHub App ID and private key.
- ``APP_URL``: the public hostname, without scheme. Must match the OAuth App
callback URL.
- ``SENTRY_DSN``: where errors are reported.


Requirements and dependencies
Expand Down
2 changes: 1 addition & 1 deletion templates/howto.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="py-3 mx-auto justify-content-center">
<p>As part of CPython’s workflow, any non-trivial change to the codebase requires an accompanying Misc/News.d file in the pull request.</p>
<p>So far, the way you would add the news entry is by using blurb command line tool, that can be installed using PyPI.</p>
<p>But now, we’re going to defy the Zen of Python’s “there should be only one way to do it”, and present you with another way to do it: <strong>blurb-it</strong>. <a href="https://blurb-it.herokuapp.com/">https://blurb-it.herokuapp.com/</a></p>
<p>But now, we’re going to defy the Zen of Python’s “there should be only one way to do it”, and present you with another way to do it: <strong>blurb-it</strong>. <a href="{{ app_protocol }}://{{ app_url }}/">{{ app_protocol }}://{{ app_url }}/</a></p>
<p>blurb-it provides a web interface for you to create news entry to your own CPython pull request. That means, you can do this from your phone, as long as you have a webbrowser and connected to the internet.</p>
<p>blurb-it is a <a href="https://github.com/apps/blurb-it">GitHub App</a> owned by The PSF. You’ll need to <a href="/install"><strong>install it</strong></a> and <strong>give write permission</strong> to your own fork’s cpython repo (so it can create the news file). Since <strong>the app will have write access to the repo</strong>, you should not install this on any other repo.</p>
<p>Please give this a try and let me know of any issues by filing a bug in the <a href="https://github.com/python/blurb_it">blurb-it repo</a>.</p>
Expand Down
17 changes: 16 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ def create_app() -> web.Application:
return app


async def test_health_check(aiohttp_client) -> None:
app = create_app()
app.router.add_get("/health", __main__.health_check)
client = await aiohttp_client(app)

response = await client.get("/health")

assert response.status == 200
assert await response.text() == "OK"


async def test_handle_get(aiohttp_client) -> None:
# Arrange
app = create_app()
Expand All @@ -47,8 +58,10 @@ async def test_handle_get(aiohttp_client) -> None:
assert "Sign in with GitHub" in text


async def test_handle_howto_get(aiohttp_client) -> None:
async def test_handle_howto_get(aiohttp_client, monkeypatch) -> None:
# Arrange
monkeypatch.setenv("APP_URL", "blurb-it.example.org")
monkeypatch.delenv("APP_PROTOCOL", raising=False)
app = create_app()

# Act
Expand All @@ -61,6 +74,8 @@ async def test_handle_howto_get(aiohttp_client) -> None:
text = await resp.text()
assert "<title>📜🤖 Blurb It!</title>" in text
assert "As part of CPython’s workflow" in text
assert "https://blurb-it.example.org/" in text
assert "herokuapp" not in text


async def test_handle_install(aiohttp_client) -> None:
Expand Down
Loading