Skip to content

Commit b4cad03

Browse files
author
Alan Christie
committed
feat: Initial project structure
1 parent f4f56ee commit b4cad03

File tree

13 files changed

+1456
-2
lines changed

13 files changed

+1456
-2
lines changed

.github/workflows/build.yaml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
name: build
3+
4+
# ---------------
5+
# Control secrets
6+
# ---------------
7+
#
8+
# At the GitHub 'organisation' or 'project' level you are expected to
9+
# have the following GitHub 'Repository Secrets' defined
10+
# (i.e. via 'Settings -> Secrets'): -
11+
#
12+
# (none)
13+
#
14+
# -----------
15+
# Environment (GitHub Environments)
16+
# -----------
17+
#
18+
# (none)
19+
20+
on:
21+
push:
22+
23+
jobs:
24+
build:
25+
runs-on: ubuntu-latest
26+
steps:
27+
- name: Inject slug/short variables
28+
uses: rlespinasse/github-slug-action@v5
29+
- name: Checkout
30+
uses: actions/checkout@v4
31+
- name: Lint Dockerfile
32+
uses: hadolint/hadolint-action@v3.1.0
33+
with:
34+
dockerfile: Dockerfile
35+
- name: Set up Python
36+
uses: actions/setup-python@v5
37+
with:
38+
python-version: '3.12'
39+
- name: Run pre-commit (all files)
40+
run: |
41+
pip install --requirement build-requirements.txt
42+
pre-commit run --all-files
43+
- name: Docker build
44+
uses: docker/build-push-action@v6
45+
with:
46+
context: .
47+
tags: ${{ github.repository_owner }}/squonk2-fastapi-ws-event-stream:${{ env.GITHUB_REF_SLUG }}

.pre-commit-config.yaml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
minimum_pre_commit_version: 4.0.0
3+
4+
repos:
5+
6+
# commit-msg hooks
7+
# ----------
8+
9+
# Conventional Commit message checker (commitizen)
10+
- repo: https://github.com/commitizen-tools/commitizen
11+
rev: v4.1.0
12+
hooks:
13+
- id: commitizen
14+
stages:
15+
- commit-msg
16+
17+
# pre-commit hooks
18+
# ----------
19+
20+
# Hooks that actively "change" files (formatters etc.)
21+
# ----------------------------------
22+
23+
# Standard pre-commit rules
24+
- repo: https://github.com/pre-commit/pre-commit-hooks
25+
rev: v5.0.0
26+
hooks:
27+
- id: check-case-conflict
28+
- id: check-docstring-first
29+
- id: check-executables-have-shebangs
30+
- id: check-shebang-scripts-are-executable
31+
- id: detect-private-key
32+
- id: end-of-file-fixer
33+
- id: trailing-whitespace
34+
args:
35+
- --markdown-linebreak-ext=md
36+
37+
# isort (in black-compatibility mode)
38+
- repo: https://github.com/pycqa/isort
39+
rev: 6.0.0
40+
hooks:
41+
- id: isort
42+
args:
43+
- --profile
44+
- black
45+
- --filter-files
46+
47+
# Black (uncompromising) Python code formatter
48+
- repo: https://github.com/psf/black
49+
rev: 25.1.0
50+
hooks:
51+
- id: black
52+
args:
53+
- --target-version
54+
- py312
55+
56+
# pylint
57+
- repo: https://github.com/pycqa/pylint
58+
rev: v3.3.4
59+
hooks:
60+
- id: pylint
61+
args:
62+
- --disable=import-error
63+
- --py-version=3.12

Dockerfile

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# The default base image
2+
ARG from_image=python:3.12.9-slim
3+
ARG image_tag=0.0.0
4+
FROM ${from_image} AS python-base
5+
6+
# Labels
7+
LABEL maintainer='Alan Christie <achristie@informaticsmatters.com>'
8+
9+
# Force the binary layer of the stdout and stderr streams
10+
# to be unbuffered
11+
ENV PYTHONUNBUFFERED 1
12+
13+
# Base directory for the application
14+
# Also used for user directory
15+
ENV APP_ROOT /home/es
16+
17+
WORKDIR ${APP_ROOT}
18+
19+
##################################################################
20+
#
21+
# Second stage, poetry installation.
22+
#
23+
##################################################################
24+
FROM python-base AS poetry-base
25+
ARG image_tag
26+
ARG POETRY_VERSION=1.8.5
27+
28+
RUN pip install --no-cache-dir poetry==${POETRY_VERSION}
29+
30+
WORKDIR /
31+
COPY poetry.lock pyproject.toml /
32+
33+
# Despite letting poetry create virtualenv here, we're not using it in
34+
# the final container, we just let poetry install the packages and
35+
# copy them later. POETRY_VIRTUALENVS_IN_PROJECT flag tells poetry to
36+
# create the venv to project's directory (.venv). This way the
37+
# location is predictable
38+
RUN POETRY_VIRTUALENVS_IN_PROJECT=true poetry install --no-root --only main --no-directory
39+
40+
##################################################################
41+
#
42+
# Final stage.
43+
# Only copy the venv with installed packages and point paths to it
44+
#
45+
##################################################################
46+
FROM python-base AS final
47+
ARG image_tag
48+
49+
COPY --from=poetry-base /.venv /.venv
50+
51+
ENV PYTHONPATH="${PYTHONPATH}:/.venv/lib/python3.13/site-packages/"
52+
ENV PATH=/.venv/bin:$PATH
53+
ENV IMAGE_TAG=${image_tag}
54+
55+
COPY app/ ./app/
56+
COPY docker-entrypoint.sh .
57+
58+
# Probes...
59+
COPY probes/*.sh .
60+
# Kubernetes lifecycle hooks...
61+
COPY hooks/*.sh .
62+
63+
# Create a base directory for file-based logging
64+
WORKDIR /log
65+
66+
# Switch to container user
67+
ENV HOME ${APP_ROOT}
68+
WORKDIR ${APP_ROOT}
69+
USER root
70+
71+
# Start the application
72+
CMD ./docker-entrypoint.sh

README.md

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,42 @@
1-
# squonk2-fastapi-ws-event-stream
2-
A FastAPI (Python) implementation of the Squonk2 (AS) Event Streaming service
1+
# Squonk2 FastAPI WebSocket Event Stream
2+
A FastAPI (Python) implementation of the Squonk2 (AS) Event Streaming service.
3+
4+
This repository is responsible for the container image that implements
5+
the even-streaming service of the Squonk2 AS platform and is deployed to the
6+
Namespace of the AS to service internal requests from the API to create, delete
7+
and connect to the internal messaging bus to stream events to a client.
8+
9+
The implementation is based on Python and the FastAPI framework, offering an _internal_
10+
API for use by the AS and a _public_ web-socket API for clients to connect to.
11+
12+
## Contributing
13+
The project uses: -
14+
15+
- [pre-commit] to enforce linting of files prior to committing them to the
16+
upstream repository
17+
- [Commitizen] to enforce a [Conventional Commit] commit message format
18+
- [Black] as a code formatter
19+
- [Poetry] as a package manager (for the b/e)
20+
21+
You **MUST** comply with these choices in order to contribute to the project.
22+
23+
To get started review the pre-commit utility and the conventional commit style
24+
and then set-up your local clone by following the **Installation** and
25+
**Quick Start** sections: -
26+
27+
poetry shell
28+
poetry install --with dev
29+
pre-commit install -t commit-msg -t pre-commit
30+
31+
Now the project's rules will run on every commit, and you can check the
32+
current health of your clone with: -
33+
34+
pre-commit run --all-files
35+
36+
---
37+
38+
[black]: https://black.readthedocs.io/en/stable
39+
[commitizen]: https://commitizen-tools.github.io/commitizen/
40+
[conventional commit]: https://www.conventionalcommits.org/en/v1.0.0/
41+
[pre-commit]: https://pre-commit.com
42+
[poetry]: https://python-poetry.org/

app/__init__.py

Whitespace-only changes.

app/app.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""The entrypoint for the Squonk2 FastAPI WebSocket service."""
2+
3+
from fastapi import FastAPI
4+
5+
# Public (event-stream) and internal (REST) services
6+
app_es = FastAPI()
7+
app_internal = FastAPI()
8+
9+
10+
# Endpoints for the 'public-facing' event-stream web-socket API ------------------------
11+
12+
13+
@app_es.get("/event-stream/")
14+
def event_stream():
15+
"""Read from the event-stream."""
16+
return {"message": "MESSAGE"}
17+
18+
19+
# Endpoints for the 'internal' event-stream management API -----------------------------
20+
21+
22+
@app_internal.post("/event-stream/")
23+
def post_es():
24+
"""Create a new event-stream."""
25+
return {"id": 1}
26+
27+
28+
@app_internal.delete("/event-stream/{es_id}")
29+
def delete_es(es_id: int):
30+
"""Destroys an existing event-stream."""
31+
return {"message": f"Destroy ES {es_id}"}

docker-compose.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
services:
3+
4+
es:
5+
build:
6+
context: .
7+
image: informaticsmatters/squonk2-fastapi-ws-event-stream:latest
8+
container_name: es
9+
ports:
10+
- '8080:8080'
11+
- '8081:8081'

docker-entrypoint.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env bash
2+
3+
#set -e
4+
5+
# Run the container using both the customer-facing WebSocket service
6+
# and the internal REST endpoint.
7+
# Done my simply launching two uvicorn instances in parallel.
8+
echo "+> Launching uvicorn..."
9+
uvicorn app.app:app_p --reload --host 0.0.0.0 --port 8080 & \
10+
uvicorn app.app:app_i --reload --host 0.0.0.0 --port 8081

hooks/pre-stop-hook.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
3+
# A container lifecycle hook.
4+
# Called from Kubernetes when the corresponding container is required to stop.
5+
# See https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination
6+
7+
# Here, we create the file '${APP_ROOT}/given.poison'.
8+
touch ${APP_ROOT}/given.poison
9+
10+
# It's the API Pod - there's no need to wait for anything.
11+
# The Pod will be set to TERMINATED and no new traffic will be presented to it.
12+
# We rely on the Pod's 'terminationGracePeriodSeconds' to actually terminate the Pod.

0 commit comments

Comments
 (0)