From e58fce30605f142a7b3e62fd21404044dcc42b25 Mon Sep 17 00:00:00 2001 From: Eric Greer Date: Tue, 2 Sep 2025 00:00:55 -0700 Subject: [PATCH 1/2] feat: add Python client package and CI --- .github/workflows/build.yml | 22 +++++++++++++++++ .github/workflows/publish.yml | 28 ++++++++++++++++++++++ Dockerfile | 1 + README.md | 31 +++++++++++++++++------- client.py | 42 ++------------------------------- kuberhealthy_client/__init__.py | 41 ++++++++++++++++++++++++++++++++ pyproject.toml | 16 +++++++++++++ 7 files changed, 133 insertions(+), 48 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/publish.yml create mode 100644 kuberhealthy_client/__init__.py create mode 100644 pyproject.toml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..ffee3b8 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,22 @@ +name: Build + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Build package + run: | + python -m pip install --upgrade pip + pip install build + python -m build + - name: Build example container + run: | + make build IMG=kuberhealthy-client:test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..6252b4f --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,28 @@ +name: Publish + +on: + release: + types: [published] + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Build package + run: | + python -m pip install --upgrade pip + pip install build + python -m build + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + - name: Attach artifacts to release + uses: softprops/action-gh-release@v1 + with: + files: dist/* diff --git a/Dockerfile b/Dockerfile index 9511043..ec6f980 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ FROM python:3.11-alpine WORKDIR /app +COPY kuberhealthy_client kuberhealthy_client COPY client.py . CMD ["python3", "/app/client.py"] diff --git a/README.md b/README.md index bc85cc2..1277a4b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,26 @@ # Python Kuberhealthy Client -This directory contains a minimal Python application that demonstrates how to -report status back to [Kuberhealthy](https://github.com/kuberhealthy/kuberhealthy). -The example loads the `KH_REPORTING_URL` and `KH_RUN_UUID` environment variables -provided to checker pods and includes commented calls to `report_ok` and -`report_error`. +This repository provides a small Python library for reporting check results back to [Kuberhealthy](https://github.com/kuberhealthy/kuberhealthy). It also includes a runnable example program and container configuration. + +## Installing + +Install the client into your own project: + +```bash +pip install kuberhealthy-client +``` + +The library exposes two helpers: + +```python +from kuberhealthy_client import report_ok, report_error + +# Environment variables KH_REPORTING_URL and KH_RUN_UUID are read automatically. +report_ok() +report_error("something went wrong") +``` + +Both functions accept optional `url` and `run_uuid` keyword arguments if you prefer to supply values directly. ## Running the example @@ -30,9 +46,8 @@ make push IMG=myrepo/example-check:latest ## Using in your own checks -1. Add your check logic to `client.py` by replacing the placeholder in `main`. - Call `report_ok()` when the check succeeds or `report_error("message")` - when it fails. +1. Add your check logic to `client.py` or your own script. Call `report_ok()` + when the check succeeds or `report_error("message")` when it fails. 2. Build and push your image as shown above. 3. Create a `KuberhealthyCheck` resource pointing at your image and apply it to any cluster where Kuberhealthy runs: diff --git a/client.py b/client.py index a046107..61934ce 100644 --- a/client.py +++ b/client.py @@ -1,45 +1,7 @@ #!/usr/bin/env python3 -"""Example Kuberhealthy client in Python.""" +"""Example Kuberhealthy client using the :mod:`kuberhealthy_client` package.""" -import json -import os -import urllib.request - - -KH_REPORTING_URL = "KH_REPORTING_URL" -KH_RUN_UUID = "KH_RUN_UUID" - - -def _get_env(name: str) -> str: - """Return the value of the environment variable *name* or raise an error.""" - value = os.getenv(name) - if not value: - raise EnvironmentError(f"{name} must be set") - return value - - -def _post_status(payload: dict) -> None: - """Send *payload* to the Kuberhealthy reporting URL.""" - url = _get_env(KH_REPORTING_URL) - run_uuid = _get_env(KH_RUN_UUID) - data = json.dumps(payload).encode("utf-8") - request = urllib.request.Request( - url, - data=data, - headers={"content-type": "application/json", "kh-run-uuid": run_uuid}, - ) - with urllib.request.urlopen(request, timeout=10) as response: # nosec B310 - response.read() - - -def report_ok() -> None: - """Report a successful check to Kuberhealthy.""" - _post_status({"OK": True, "Errors": []}) - - -def report_error(message: str) -> None: - """Report a failure to Kuberhealthy with *message* as the error.""" - _post_status({"OK": False, "Errors": [message]}) +from kuberhealthy_client import report_ok, report_error def main() -> None: diff --git a/kuberhealthy_client/__init__.py b/kuberhealthy_client/__init__.py new file mode 100644 index 0000000..3955cd7 --- /dev/null +++ b/kuberhealthy_client/__init__.py @@ -0,0 +1,41 @@ +"""Lightweight client for reporting check results to Kuberhealthy.""" + +from __future__ import annotations + +import json +import os +import urllib.request +from typing import Optional + +KH_REPORTING_URL = "KH_REPORTING_URL" +KH_RUN_UUID = "KH_RUN_UUID" + +def _get_env(name: str) -> str: + """Return the value of the environment variable *name* or raise an error.""" + value = os.getenv(name) + if not value: + raise EnvironmentError(f"{name} must be set") + return value + +def _post_status(payload: dict, *, url: Optional[str] = None, run_uuid: Optional[str] = None) -> None: + """Send *payload* to the Kuberhealthy reporting URL.""" + url = url or _get_env(KH_REPORTING_URL) + run_uuid = run_uuid or _get_env(KH_RUN_UUID) + data = json.dumps(payload).encode("utf-8") + request = urllib.request.Request( + url, + data=data, + headers={"content-type": "application/json", "kh-run-uuid": run_uuid}, + ) + with urllib.request.urlopen(request, timeout=10) as response: # nosec B310 + response.read() + +def report_ok(*, url: Optional[str] = None, run_uuid: Optional[str] = None) -> None: + """Report a successful check to Kuberhealthy.""" + _post_status({"OK": True, "Errors": []}, url=url, run_uuid=run_uuid) + +def report_error(message: str, *, url: Optional[str] = None, run_uuid: Optional[str] = None) -> None: + """Report a failure to Kuberhealthy with *message* as the error.""" + _post_status({"OK": False, "Errors": [message]}, url=url, run_uuid=run_uuid) + +__all__ = ["report_ok", "report_error", "KH_REPORTING_URL", "KH_RUN_UUID"] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..36ab018 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "kuberhealthy-client" +version = "0.1.0" +description = "Python client for reporting check results to Kuberhealthy" +readme = "README.md" +authors = [{name = "Kuberhealthy"}] +license = {text = "Apache-2.0"} +requires-python = ">=3.8" + +[tool.setuptools.packages.find] +where = ["."] +include = ["kuberhealthy_client"] From c807f783ffc6b591efc0f668813ed5076a2f27a3 Mon Sep 17 00:00:00 2001 From: Eric Greer Date: Tue, 2 Sep 2025 00:10:27 -0700 Subject: [PATCH 2/2] refactor: isolate example client --- Dockerfile | 4 ++-- README.md | 6 +++--- client.py => example/client.py | 0 3 files changed, 5 insertions(+), 5 deletions(-) rename client.py => example/client.py (100%) diff --git a/Dockerfile b/Dockerfile index ec6f980..eadedb4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,6 @@ FROM python:3.11-alpine WORKDIR /app COPY kuberhealthy_client kuberhealthy_client -COPY client.py . +COPY example example -CMD ["python3", "/app/client.py"] +CMD ["python3", "/app/example/client.py"] diff --git a/README.md b/README.md index 1277a4b..cbc2ac9 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,10 @@ Both functions accept optional `url` and `run_uuid` keyword arguments if you pre ## Running the example Set the `KH_REPORTING_URL` and `KH_RUN_UUID` environment variables, add your -check logic to `client.py`, and then run: +check logic to `example/client.py`, and then run: ```bash -python3 client.py +python3 example/client.py ``` Within the `main` function, uncomment either `report_ok()` or @@ -46,7 +46,7 @@ make push IMG=myrepo/example-check:latest ## Using in your own checks -1. Add your check logic to `client.py` or your own script. Call `report_ok()` +1. Add your check logic to `example/client.py` or your own script. Call `report_ok()` when the check succeeds or `report_error("message")` when it fails. 2. Build and push your image as shown above. 3. Create a `KuberhealthyCheck` resource pointing at your image and apply it to any diff --git a/client.py b/example/client.py similarity index 100% rename from client.py rename to example/client.py