From 63f5a52b7a3fca753dd2d3570af75b54abac563c Mon Sep 17 00:00:00 2001 From: Curtis Mason Date: Tue, 18 Aug 2020 12:57:59 -0700 Subject: [PATCH 1/7] Added additional cloudevent error handling Signed-off-by: Curtis Mason --- run/events-pubsub/main.py | 28 ++++++++++++++++++++-------- run/events-pubsub/requirements.txt | 1 + 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/run/events-pubsub/main.py b/run/events-pubsub/main.py index 01e4fe10f9b..997a0ddbaff 100644 --- a/run/events-pubsub/main.py +++ b/run/events-pubsub/main.py @@ -13,6 +13,8 @@ # limitations under the License. # [START run_events_pubsub_server_setup] +from cloudevents.http import from_http +import cloudevents.exceptions as cloud_exceptions import base64 import os @@ -28,13 +30,24 @@ # [START run_events_pubsub_handler] @app.route('/', methods=['POST']) def index(): - for field in required_fields: - if field not in request.headers: - errmsg = f'Bad Request: missing required header {field}' - print(errmsg) - return errmsg, 400 + # Create CloudEvent from HTTP headers and body + try: + event = from_http(request.headers, request.get_data()) + + except cloud_exceptions.MissingRequiredFields as e: + print(f"cloudevents.exceptions.MissingRequiredFields: {e}") + return f"Failed to find all required cloudevent fields. ", 400 + + except cloud_exceptions.InvalidStructuredJSON as e: + print(f"cloudevents.exceptions.InvalidStructuredJSON: {e}") + return f"Could not deserialize the payload as JSON. ", 400 + + except cloud_exceptions.InvalidRequiredFields as e: + print(f"cloudevents.exceptions.InvalidRequiredFields: {e}") + return f"Request contained invalid required cloudevent fields. ", 400 + + envelope = event.data - envelope = request.get_json() if not envelope: msg = 'no Pub/Sub message received' print(f'error: {msg}') @@ -51,8 +64,7 @@ def index(): if isinstance(pubsub_message, dict) and 'data' in pubsub_message: name = base64.b64decode(pubsub_message['data']).decode('utf-8').strip() - ce_id = request.headers.get('Ce-Id') - resp = f'Hello, {name}! ID: {ce_id}' + resp = f'Hello, {name}! ID: {event['id']}' print(resp) return (resp, 200) # [END run_events_pubsub_handler] diff --git a/run/events-pubsub/requirements.txt b/run/events-pubsub/requirements.txt index 7179b09a0e2..9442f6bd618 100644 --- a/run/events-pubsub/requirements.txt +++ b/run/events-pubsub/requirements.txt @@ -1,2 +1,3 @@ Flask==1.1.2 gunicorn==20.0.4 +cloudevents>=1.1.0 From a9ce2ba78a98fe293e7f8b532be7d5afb7b63e92 Mon Sep 17 00:00:00 2001 From: Curtis Mason Date: Tue, 18 Aug 2020 17:44:26 -0700 Subject: [PATCH 2/7] Added cloudevents from github.com/cloudevents/sdk-python Signed-off-by: Curtis Mason --- run/events-pubsub/main.py | 2 +- run/events-pubsub/main_test.py | 16 +++++++++++----- run/events-pubsub/requirements-test.txt | 2 ++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/run/events-pubsub/main.py b/run/events-pubsub/main.py index 997a0ddbaff..8f781fcdeed 100644 --- a/run/events-pubsub/main.py +++ b/run/events-pubsub/main.py @@ -64,7 +64,7 @@ def index(): if isinstance(pubsub_message, dict) and 'data' in pubsub_message: name = base64.b64decode(pubsub_message['data']).decode('utf-8').strip() - resp = f'Hello, {name}! ID: {event['id']}' + resp = f"Hello, {name}! ID: {event['id']}" print(resp) return (resp, 200) # [END run_events_pubsub_handler] diff --git a/run/events-pubsub/main_test.py b/run/events-pubsub/main_test.py index 05940e17749..7a05cedc871 100644 --- a/run/events-pubsub/main_test.py +++ b/run/events-pubsub/main_test.py @@ -22,9 +22,14 @@ import main -required_fields = ['Ce-Id', 'Ce-Source', 'Ce-Type', 'Ce-Specversion'] +required_fields = ['ce-id', 'ce-source', 'ce-type', 'ce-specversion'] -header_data = {field: str(uuid4()) for field in required_fields} +header_data = { + "ce-id": str(uuid4), + "ce-type": "com.pytest.sample.event", + "ce-source": "", + "ce-specversion": "1.0" +} @pytest.fixture @@ -53,7 +58,8 @@ def test_minimally_valid_message(client, capsys): assert r.status_code == 200 out, _ = capsys.readouterr() - ce_id = header_data['Ce-Id'] + ce_id = header_data['ce-id'] + print(out) assert f'Hello, World! ID: {ce_id}' in out @@ -65,7 +71,7 @@ def test_populated_message(client, capsys): assert r.status_code == 200 out, _ = capsys.readouterr() - ce_id = header_data['Ce-Id'] + ce_id = header_data['ce-id'] assert f'Hello, {name}! ID: {ce_id}' in out @@ -78,4 +84,4 @@ def test_missing_required_fields(client, capsys): assert r.status_code == 400 out, _ = capsys.readouterr() - assert f'Bad Request: missing required header {field}' in out + assert 'MissingRequiredFields' in out or 'InvalidStructuredJSON' in out diff --git a/run/events-pubsub/requirements-test.txt b/run/events-pubsub/requirements-test.txt index 7e460c8c866..0b21248c7c2 100644 --- a/run/events-pubsub/requirements-test.txt +++ b/run/events-pubsub/requirements-test.txt @@ -1 +1,3 @@ pytest==6.0.1 +cloudevents>=1.1.0 +Flask==1.1.2 From c5d904300392115775243b8dd0fd4209a11b9bd9 Mon Sep 17 00:00:00 2001 From: Curtis Mason Date: Thu, 20 Aug 2020 07:02:25 -0700 Subject: [PATCH 3/7] cloudevents version bump 1.2.0 Signed-off-by: Curtis Mason --- run/events-pubsub/README.md | 3 ++- run/events-pubsub/main_test.py | 22 ++++++++++------------ run/events-pubsub/requirements-test.txt | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/run/events-pubsub/README.md b/run/events-pubsub/README.md index f29e511dc80..caf6e66935b 100644 --- a/run/events-pubsub/README.md +++ b/run/events-pubsub/README.md @@ -1,6 +1,7 @@ # Events for Cloud Run – Pub/Sub tutorial -This sample shows how to create a service that processes Pub/Sub events. +This sample shows how to create a service that processes Pub/Sub events using +[CloudEvents](https://github.com/cloudevents/sdk-python). ## Setup diff --git a/run/events-pubsub/main_test.py b/run/events-pubsub/main_test.py index 7a05cedc871..391138b8777 100644 --- a/run/events-pubsub/main_test.py +++ b/run/events-pubsub/main_test.py @@ -22,9 +22,7 @@ import main -required_fields = ['ce-id', 'ce-source', 'ce-type', 'ce-specversion'] - -header_data = { +binary_headers = { "ce-id": str(uuid4), "ce-type": "com.pytest.sample.event", "ce-source": "", @@ -39,26 +37,26 @@ def client(): def test_empty_payload(client): - r = client.post('/', json='', headers=header_data) + r = client.post('/', json='', headers=binary_headers) assert r.status_code == 400 def test_invalid_payload(client): - r = client.post('/', json={'nomessage': 'invalid'}, headers=header_data) + r = client.post('/', json={'nomessage': 'invalid'}, headers=binary_headers) assert r.status_code == 400 def test_invalid_mimetype(client): - r = client.post('/', json="{ message: true }", headers=header_data) + r = client.post('/', json="{ message: true }", headers=binary_headers) assert r.status_code == 400 def test_minimally_valid_message(client, capsys): - r = client.post('/', json={'message': True}, headers=header_data) + r = client.post('/', json={'message': True}, headers=binary_headers) assert r.status_code == 200 out, _ = capsys.readouterr() - ce_id = header_data['ce-id'] + ce_id = binary_headers['ce-id'] print(out) assert f'Hello, World! ID: {ce_id}' in out @@ -67,17 +65,17 @@ def test_populated_message(client, capsys): name = str(uuid4()) data = base64.b64encode(name.encode()).decode() - r = client.post('/', json={'message': {'data': data}}, headers=header_data) + r = client.post('/', json={'message': {'data': data}}, headers=binary_headers) assert r.status_code == 200 out, _ = capsys.readouterr() - ce_id = header_data['ce-id'] + ce_id = binary_headers['ce-id'] assert f'Hello, {name}! ID: {ce_id}' in out def test_missing_required_fields(client, capsys): - for field in required_fields: - test_headers = copy.copy(header_data) + for field in binary_headers: + test_headers = copy.copy(binary_headers) test_headers.pop(field) r = client.post('/', headers=test_headers) diff --git a/run/events-pubsub/requirements-test.txt b/run/events-pubsub/requirements-test.txt index 0b21248c7c2..8343aaa9390 100644 --- a/run/events-pubsub/requirements-test.txt +++ b/run/events-pubsub/requirements-test.txt @@ -1,3 +1,3 @@ pytest==6.0.1 -cloudevents>=1.1.0 +cloudevents>=1.2.0 Flask==1.1.2 From 736346b3053da372152bee86655ae4d475cb9143 Mon Sep 17 00:00:00 2001 From: Curtis Mason Date: Thu, 20 Aug 2020 09:15:06 -0700 Subject: [PATCH 4/7] lint fixes Signed-off-by: Curtis Mason --- run/events-pubsub/main.py | 12 +++++++----- run/events-pubsub/main_test.py | 4 ++-- run/events-pubsub/requirements.txt | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/run/events-pubsub/main.py b/run/events-pubsub/main.py index 8f781fcdeed..7b6bdd8ca7c 100644 --- a/run/events-pubsub/main.py +++ b/run/events-pubsub/main.py @@ -13,11 +13,13 @@ # limitations under the License. # [START run_events_pubsub_server_setup] -from cloudevents.http import from_http -import cloudevents.exceptions as cloud_exceptions import base64 + import os +import cloudevents.exceptions as cloud_exceptions +from cloudevents.http import from_http + from flask import Flask, request @@ -36,15 +38,15 @@ def index(): except cloud_exceptions.MissingRequiredFields as e: print(f"cloudevents.exceptions.MissingRequiredFields: {e}") - return f"Failed to find all required cloudevent fields. ", 400 + return "Failed to find all required cloudevent fields. ", 400 except cloud_exceptions.InvalidStructuredJSON as e: print(f"cloudevents.exceptions.InvalidStructuredJSON: {e}") - return f"Could not deserialize the payload as JSON. ", 400 + return "Could not deserialize the payload as JSON. ", 400 except cloud_exceptions.InvalidRequiredFields as e: print(f"cloudevents.exceptions.InvalidRequiredFields: {e}") - return f"Request contained invalid required cloudevent fields. ", 400 + return "Request contained invalid required cloudevent fields. ", 400 envelope = event.data diff --git a/run/events-pubsub/main_test.py b/run/events-pubsub/main_test.py index 391138b8777..b32aac2e8d7 100644 --- a/run/events-pubsub/main_test.py +++ b/run/events-pubsub/main_test.py @@ -57,7 +57,7 @@ def test_minimally_valid_message(client, capsys): out, _ = capsys.readouterr() ce_id = binary_headers['ce-id'] - print(out) + assert f'Hello, World! ID: {ce_id}' in out @@ -82,4 +82,4 @@ def test_missing_required_fields(client, capsys): assert r.status_code == 400 out, _ = capsys.readouterr() - assert 'MissingRequiredFields' in out or 'InvalidStructuredJSON' in out + assert 'MissingRequiredFields' in out diff --git a/run/events-pubsub/requirements.txt b/run/events-pubsub/requirements.txt index 9442f6bd618..05d6749c0d9 100644 --- a/run/events-pubsub/requirements.txt +++ b/run/events-pubsub/requirements.txt @@ -1,3 +1,3 @@ Flask==1.1.2 gunicorn==20.0.4 -cloudevents>=1.1.0 +cloudevents>=1.2.0 From 3aed6bdfe00265e6e69237b6e85dfdab99337538 Mon Sep 17 00:00:00 2001 From: Curtis Mason Date: Thu, 20 Aug 2020 11:18:02 -0700 Subject: [PATCH 5/7] README nit Signed-off-by: Curtis Mason --- run/events-pubsub/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run/events-pubsub/README.md b/run/events-pubsub/README.md index caf6e66935b..14e2c434c49 100644 --- a/run/events-pubsub/README.md +++ b/run/events-pubsub/README.md @@ -1,7 +1,7 @@ # Events for Cloud Run – Pub/Sub tutorial This sample shows how to create a service that processes Pub/Sub events using -[CloudEvents](https://github.com/cloudevents/sdk-python). +[the CloudEvents SDK](https://github.com/cloudevents/sdk-python). ## Setup From 601d35900d0224ccecc85df243474b8958e4bf56 Mon Sep 17 00:00:00 2001 From: Curtis Mason Date: Thu, 20 Aug 2020 11:28:50 -0700 Subject: [PATCH 6/7] cloudevents==1.2.0 Signed-off-by: Curtis Mason --- run/events-pubsub/requirements-test.txt | 2 +- run/events-pubsub/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/run/events-pubsub/requirements-test.txt b/run/events-pubsub/requirements-test.txt index 8343aaa9390..6ecbd95f2a9 100644 --- a/run/events-pubsub/requirements-test.txt +++ b/run/events-pubsub/requirements-test.txt @@ -1,3 +1,3 @@ pytest==6.0.1 -cloudevents>=1.2.0 +cloudevents==1.2.0 Flask==1.1.2 diff --git a/run/events-pubsub/requirements.txt b/run/events-pubsub/requirements.txt index 05d6749c0d9..3a5e57bf96a 100644 --- a/run/events-pubsub/requirements.txt +++ b/run/events-pubsub/requirements.txt @@ -1,3 +1,3 @@ Flask==1.1.2 gunicorn==20.0.4 -cloudevents>=1.2.0 +cloudevents==1.2.0 From 23903c86b3f5625eb47638b80639951f49bfdd70 Mon Sep 17 00:00:00 2001 From: Curtis Mason Date: Thu, 20 Aug 2020 17:25:56 -0400 Subject: [PATCH 7/7] removed flask and cloudevents from req-test Signed-off-by: Curtis Mason --- run/events-pubsub/requirements-test.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/run/events-pubsub/requirements-test.txt b/run/events-pubsub/requirements-test.txt index 6ecbd95f2a9..7e460c8c866 100644 --- a/run/events-pubsub/requirements-test.txt +++ b/run/events-pubsub/requirements-test.txt @@ -1,3 +1 @@ pytest==6.0.1 -cloudevents==1.2.0 -Flask==1.1.2