From 22d30ff55bc8dbdc30d11b831eab945c04592446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Thu, 24 Sep 2026 23:05:26 +0200 Subject: [PATCH 1/3] feat: add the --debug CLI flag --- scim2_server/cli.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scim2_server/cli.py b/scim2_server/cli.py index e5e9be4..75cbd75 100644 --- a/scim2_server/cli.py +++ b/scim2_server/cli.py @@ -44,9 +44,14 @@ def main(): type=argparse.FileType("w"), help="Dump resources to a JSON file on exit", ) + parser.add_argument( + "--debug", + action="store_true", + help="Enable the interactive debugger, the reloader and the logging of the WSGI environment", + ) args = parser.parse_args() - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) from werkzeug.serving import run_simple @@ -77,7 +82,8 @@ def main(): for bearer_token in args.bearer_token: app.register_bearer_token(bearer_token) - app = log_environ(app) + if args.debug: + app = log_environ(app) if args.reverse_proxy: app = ProxyFix(app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1) @@ -85,8 +91,8 @@ def main(): args.hostname, args.port, app, - use_debugger=True, - use_reloader=True, + use_debugger=args.debug, + use_reloader=args.debug, ) if args.dump_resources: From 6d6e57bb67b666c1f42633f17e299e092be0f1d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Thu, 24 Sep 2026 23:07:44 +0200 Subject: [PATCH 2/3] feat: build a Docker image on tags --- .github/workflows/release.yml | 31 +++++++++++++++++++++++++++++++ .github/workflows/tests.yaml | 20 ++++++++++++++++++++ Containerfile | 23 +++++++++++++++++++++++ README.md | 14 +++++++++++++- 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 Containerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f5a062..17b6abc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,3 +49,34 @@ jobs: - name: Push build artifacts to PyPI uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 + + container: + name: publish container image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0 + - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + - uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: . + file: Containerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 7185c74..879d770 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -53,3 +53,23 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + + container: + name: container image + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Build image + run: docker build --file Containerfile --tag scim2-server:ci . + - name: Start container + run: docker run --detach --name scim2-server --publish 8080:8080 scim2-server:ci --bearer-token secret + - name: Check the server answers + run: | + curl --fail --silent --retry 30 --retry-delay 1 --retry-all-errors --output /dev/null http://127.0.0.1:8080/v2/ServiceProviderConfig + test "$(curl --silent --output /dev/null --write-out '%{http_code}' http://127.0.0.1:8080/v2/Users)" = 401 + curl --fail --silent --output /dev/null --header "Authorization: Bearer secret" http://127.0.0.1:8080/v2/Users + - name: Stop container + run: timeout 5 docker stop scim2-server + - name: Show logs + if: always() + run: docker logs scim2-server diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..0965994 --- /dev/null +++ b/Containerfile @@ -0,0 +1,23 @@ +FROM ghcr.io/astral-sh/uv:python3.14-trixie-slim AS builder + +ENV UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + UV_PYTHON_DOWNLOADS=0 + +WORKDIR /app +COPY pyproject.toml uv.lock README.md LICENSE ./ +RUN uv sync --locked --no-dev --no-install-project +COPY scim2_server ./scim2_server +RUN uv sync --locked --no-dev --no-editable + +FROM python:3.14-slim-trixie + +COPY --from=builder /app/.venv /app/.venv +ENV PATH="/app/.venv/bin:$PATH" + +USER nobody +EXPOSE 8080 +# Werkzeug stops gracefully on KeyboardInterrupt, which lets --dump-resources be written. +STOPSIGNAL SIGINT +ENTRYPOINT ["scim2-server", "--hostname", "0.0.0.0"] +CMD ["--port", "8080"] diff --git a/README.md b/README.md index 7a7ce75..c0f2e92 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The only optional feature currently missing is support for Bulk operations ([RFC ## Usage ```shell -$ scim2-server [-h] [--schema SCHEMA] [--resource-type RESOURCE_TYPE] [--bearer-token BEARER_TOKEN] [--hostname HOSTNAME] [--port PORT] [--reverse-proxy] [--dump-resources DUMP_RESOURCES] +$ scim2-server [-h] [--schema SCHEMA] [--resource-type RESOURCE_TYPE] [--bearer-token BEARER_TOKEN] [--hostname HOSTNAME] [--port PORT] [--reverse-proxy] [--dump-resources DUMP_RESOURCES] [--debug] ``` - `-h`/`--help`: Show help message @@ -30,6 +30,18 @@ $ scim2-server [-h] [--schema SCHEMA] [--resource-type RESOURCE_TYPE] [--bearer- - `--hostname`: The hostname to listen on. Defaults to `127.0.0.1`. - `--port`: The port to listen on. Defaults to `8080`. - `--dump-resources`: Dump a JSON document containing all resources when the provider exits normally. +- `--debug`: Enable the interactive Werkzeug debugger, the reloader and the logging of the WSGI environment of each request. The debugger allows arbitrary code execution and the environment contains the bearer tokens, so never use this option on a server reachable by others. + +### Container + +A container image is published on the GitHub container registry for each release. +The server listens on `0.0.0.0:8080` inside the container, and the command line arguments are passed to `scim2-server`: + +```shell +$ docker run --publish 8080:8080 ghcr.io/python-scim/scim2-server --bearer-token secret +``` + +To build the image yourself, use `docker build --file Containerfile .` or `podman build .`. ## Notes From ae69e4f9555029b9812db9cfcf5ab93634fbc3b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Thu, 24 Sep 2026 23:09:02 +0200 Subject: [PATCH 3/3] chore: add .containerignore --- .containerignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .containerignore diff --git a/.containerignore b/.containerignore new file mode 100644 index 0000000..9512dda --- /dev/null +++ b/.containerignore @@ -0,0 +1,7 @@ +* +!pyproject.toml +!uv.lock +!README.md +!LICENSE +!scim2_server +**/__pycache__