Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Secure connection + v20.10 support #243

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,6 @@ ENV/
.mypy_cache
*.orig
.venv

# docker compose volumes
certs
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
sudo: required
dist: focal
language: python

matrix:
include:
- python: "3.10"
Expand Down
38 changes: 22 additions & 16 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
BLACK_EXCLUSION=photonpump/__init__.py|photonpump/_version.py|versioneer.py|.tox|.venv
BLACK_EXCLUSION=photonpump/__init__.py|photonpump/_version.py|versioneer.py|.tox|.venv|.pb2
SHELL = /bin/bash
default: fast_tests
travis: check_lint tox
Expand Down Expand Up @@ -26,19 +26,25 @@ continous_test:
PYASYNCIODEBUG=1 ptw

cleanup:
- docker rm -f eventstore_local
- docker rm -f eventstore_local_noauth

eventstore_docker:
docker run -d --name eventstore_local -p 2113:2113 -p 1113:1113 eventstore/eventstore:release-5.0.8
docker run -d --name eventstore_local_noauth -p 22113:2113 -p 11113:1113 eventstore/eventstore:release-5.0.8
for i in {1..10}; do curl -f -i "http://127.0.0.1:2113/users" --user admin:changeit && break || sleep 1; done
for i in {1..10}; do curl -f -i "http://127.0.0.1:22113/users" --user admin:changeit && break || sleep 1; done
curl -f -i "http://127.0.0.1:2113/streams/%24settings" \
--user admin:changeit \
-H "Content-Type: application/vnd.eventstore.events+json" \
-d @default-acl.json
curl -f -i "http://127.0.0.1:2113/users" \
docker-compose down -v

run_compose:
docker-compose up -d


create_users:
until curl -f --insecure https://localhost:2111/health/live; do sleep 1; done
until curl -k -f -i "https://127.0.0.1:2111/streams/%24settings" \
--user admin:changeit \
-H "Content-Type: application/json" \
-d @test-user.json
-H "Content-Type: application/vnd.eventstore.events+json" \
-d @default-acl.json; do sleep 1; done
until curl -k -f -i "https://127.0.0.1:2111/users" \
--user admin:changeit \
-H "Content-Type: application/json" \
-d @test-user.json; do sleep 1; done

eventstore_docker: run_compose create_users


proto-compile:
protoc -I=proto --python_out=photonpump --mypy_out=photonpump proto/*
34 changes: 31 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,34 @@ The `photonpump.connect` function returns an async context manager so that the c
>>> await client.ping()
>>> await client.close()

Secure connection
~~~~~~~~~~~~~~~~~

Pass `~ssl.SSLContext` class to `~photonpump.Client`.

>>> import ssl
>>> import asyncio
>>>
>>> from photonpump import connect
>>>
>>> context = ssl.SSLContext(ssl.PROTOCOL_TLS)
>>> context.load_verify_locations("certs/ca/ca.crt")
>>> context.load_cert_chain(certfile="certs/ca/ca.crt", keyfile="certs/ca/ca.key")
>>> context.verify_mode = ssl.CERT_REQUIRED
>>>
>>> async def main():
>>> async with connect(
>>> port=1111,
>>> host="localhost",
>>> username="admin",
>>> password="changeit",
>>> sslcontext=context, # or sslcontext=True if self-signed cert is not used
>>> ) as conn:
>>> await conn.ping()
>>>
>>>
>>> asyncio.run(main())

Reading and Writing single events
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -191,7 +219,7 @@ Persistent Subscriptions

Sometimes we want to watch a stream continuously and be notified when a new event occurs. Eventstore supports volatile and persistent subscriptions for this use case.

A persistent subscription stores its state on the server. When your application restarts, you can connect to the subscription again and continue where you left off. Multiple clients can connect to the same persistent subscription to support competing consumer scenarios. To support these features, persistent subscriptions have to run against the master node of an Eventstore cluster.
A persistent subscription stores its state on the server. When your application restarts, you can connect to the subscription again and continue where you left off. Multiple clients can connect to the same persistent subscription to support competing consumer scenarios. To support these features, persistent subscriptions have to run against the leader node of an Eventstore cluster.

Firstly, we need to `create the subscription <photonpump.connection.Client.create_subscription>`.

Expand Down Expand Up @@ -225,9 +253,9 @@ Volatile subsciptions do not support event acknowledgement.
High-Availability Scenarios
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Eventstore supports an HA-cluster deployment topology. In this scenario, Eventstore runs a master node and multiple slaves. Some operations, particularly persistent subscriptions and projections, are handled only by the master node. To connect to an HA-cluster and automatically find the master node, photonpump supports cluster discovery.
Eventstore supports an HA-cluster deployment topology. In this scenario, Eventstore runs a leader node and multiple followers. Some operations, particularly persistent subscriptions and projections, are handled only by the leader node. To connect to an HA-cluster and automatically find the leader node, photonpump supports cluster discovery.

The cluster discovery interrogates eventstore gossip to find the active master. You can provide the IP of a maching in the cluster, or a DNS name that resolves to some members of the cluster, and photonpump will discover the others.
The cluster discovery interrogates eventstore gossip to find the active leader. You can provide the IP of a machine in the cluster, or a DNS name that resolves to some members of the cluster, and photonpump will discover the others.

>>> async def connect_to_cluster(hostname_or_ip, port=2113):
>>> with connect(discovery_host=hostname_or_ip, discovery_port=2113) as c:
Expand Down
106 changes: 106 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
version: '3.5'

services:
volumes-provisioner:
image: "hasnat/volumes-provisioner"
environment:
PROVISION_DIRECTORIES: "1000:1000:0755:/tmp/certs"
volumes:
- "./certs:/tmp/certs"
network_mode: "none"

setup-certs:
image: eventstore/es-gencert-cli:1.0.2
entrypoint: bash
user: "1000:1000"
command: >
-c "mkdir -p ./certs && cd /certs
&& es-gencert-cli create-ca
&& es-gencert-cli create-node -out ./node1 --dns-names node1.eventstore,localhost
&& es-gencert-cli create-node -out ./node2 --dns-names node2.eventstore,localhost
&& es-gencert-cli create-node -out ./node3 --dns-names node3.eventstore,localhost
&& find . -type f -print0 | xargs -0 chmod 666"
container_name: setup-certs
volumes:
- ./certs:/certs
depends_on:
- volumes-provisioner

node1.eventstore: &template
image: ghcr.io/eventstore/eventstore:20.10.5-buster-slim
container_name: node1.eventstore
env_file:
- vars.env
environment:
- EVENTSTORE_EXT_HOST_ADVERTISE_AS=node1.eventstore
- EVENTSTORE_INT_HOST_ADVERTISE_AS=node1.eventstore
- EVENTSTORE_GOSSIP_SEED=node2.eventstore:2113,node3.eventstore:2113
- EVENTSTORE_CERTIFICATE_FILE=/certs/node1/node.crt
- EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/certs/node1/node.key
- EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=2111
- EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=1111
healthcheck:
test:
[
'CMD-SHELL',
'curl --fail --insecure https://node1.eventstore:2113/health/live || exit 1',
]
interval: 5s
timeout: 5s
retries: 24
ports:
- 1111:1113
- 2111:2113
volumes:
- ./certs:/certs
depends_on:
- setup-certs
restart: always

node2.eventstore:
<<: *template
container_name: node2.eventstore
environment:
- EVENTSTORE_EXT_HOST_ADVERTISE_AS=node2.eventstore
- EVENTSTORE_INT_HOST_ADVERTISE_AS=node2.eventstore
- EVENTSTORE_GOSSIP_SEED=node1.eventstore:2113,node3.eventstore:2113
- EVENTSTORE_CERTIFICATE_FILE=/certs/node2/node.crt
- EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/certs/node2/node.key
- EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=2112
- EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=1112
healthcheck:
test:
[
'CMD-SHELL',
'curl --fail --insecure https://node2.eventstore:2113/health/live || exit 1',
]
interval: 5s
timeout: 5s
retries: 24
ports:
- 1112:1113
- 2112:2113

node3.eventstore:
<<: *template
container_name: node3.eventstore
environment:
- EVENTSTORE_EXT_HOST_ADVERTISE_AS=node3.eventstore
- EVENTSTORE_INT_HOST_ADVERTISE_AS=node3.eventstore
- EVENTSTORE_GOSSIP_SEED=node1.eventstore:2113,node2.eventstore:2113
- EVENTSTORE_CERTIFICATE_FILE=/certs/node3/node.crt
- EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/certs/node3/node.key
- EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=2113
- EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=1113
healthcheck:
test:
[
'CMD-SHELL',
'curl --fail --insecure https://node3.eventstore:2113/health/live || exit 1',
]
interval: 5s
timeout: 5s
retries: 24
ports:
- 1113:1113
- 2113:2113
Loading