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
7 changes: 7 additions & 0 deletions .containerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*
!pyproject.toml
!uv.lock
!README.md
!LICENSE
!scim2_server
**/__pycache__
31 changes: 31 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
20 changes: 20 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
23 changes: 23 additions & 0 deletions Containerfile
Original file line number Diff line number Diff line change
@@ -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"]
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
14 changes: 10 additions & 4 deletions scim2_server/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -77,16 +82,17 @@ 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)

run_simple(
args.hostname,
args.port,
app,
use_debugger=True,
use_reloader=True,
use_debugger=args.debug,
use_reloader=args.debug,
)

if args.dump_resources:
Expand Down
Loading