Skip to content

Commit 29e4cd6

Browse files
Add Cloud Run to Python Samples (GoogleCloudPlatform#2247)
* run/pubsub: add pubsub push sample app * run: add product section README * run: add kokoro integration * Update run/pubsub/README.md Co-Authored-By: Averi Kitsch <[email protected]>
1 parent 822a332 commit 29e4cd6

File tree

10 files changed

+304
-0
lines changed

10 files changed

+304
-0
lines changed

.kokoro/presubmit_tests_run.cfg

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Format: //devtools/kokoro/config/proto/build.proto
2+
3+
# Download secrets from Cloud Storage.
4+
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples"
5+
6+
# Tell the trampoline which build file to use.
7+
env_vars: {
8+
key: "TRAMPOLINE_BUILD_FILE"
9+
value: "github/python-docs-samples/.kokoro/system_tests.sh"
10+
}
11+
12+
env_vars: {
13+
key: "NOX_SESSION"
14+
value: "run"
15+
}

run/README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Google Cloud Run Python Samples
2+
3+
[![Open in Cloud Shell][shell_img]][shell_link]
4+
5+
[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png
6+
[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=blog/README.md
7+
8+
This directory contains samples for [Google Cloud Run](https://cloud.run). [Cloud Run][run_docs] runs stateless [containers](https://cloud.google.com/containers/) on a fully managed environment or in your own GKE cluster.
9+
10+
## Samples
11+
12+
| Sample | Description | Deploy |
13+
| ------------------------------- | ------------------------ | ------------- |
14+
|[Hello World][helloworld]&nbsp;&#10149; | Quickstart | [<img src="https://storage.googleapis.com/cloudrun/button.svg" alt="Run on Google Cloud" height="30">][run_button_helloworld] |
15+
|[Cloud Pub/Sub][pubsub] | Handling Pub/Sub push messages | [<img src="https://storage.googleapis.com/cloudrun/button.svg" alt="Run on Google Cloud" height="30">][run_button_pubsub] |
16+
17+
For more Cloud Run samples beyond Python, see the main list in the [Cloud Run Samples repository](https://github.com/GoogleCloudPlatform/cloud-run-samples).
18+
19+
## Setup
20+
21+
1. [Set up for Cloud Run development](https://cloud.google.com/run/docs/setup)
22+
23+
2. Clone this repository:
24+
25+
```
26+
git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git
27+
```
28+
29+
Note: Some samples in the list above are hosted in other repositories. They are noted with the symbol "&#10149;".
30+
31+
32+
## How to run a sample locally
33+
34+
1. [Install docker locally](https://docs.docker.com/install/)
35+
36+
2. [Build the sample container](https://cloud.google.com/run/docs/building/containers#building_locally_and_pushing_using_docker):
37+
38+
```
39+
export SAMPLE=$sample
40+
cd $SAMPLE
41+
docker build --tag $sample .
42+
```
43+
44+
3. [Run containers locally](https://cloud.google.com/run/docs/testing/local)
45+
46+
With the built container:
47+
48+
```
49+
PORT=8080 && docker run --rm -p 8080:${PORT} -e PORT=${PORT} $SAMPLE
50+
```
51+
52+
Overriding the built container with local code:
53+
54+
```
55+
PORT=8080 && docker run --rm \
56+
-p 8080:${PORT} -e PORT=${PORT} \
57+
-v $PWD:/app $SAMPLE
58+
```
59+
60+
Injecting your service account key:
61+
62+
```
63+
export SA_KEY_NAME=my-key-name-123
64+
PORT=8080 && docker run --rm \
65+
-p 8080:${PORT} -e PORT=${PORT} \
66+
-e GOOGLE_APPLICATION_CREDENTIALS=/tmp/keys/${SA_KEY_NAME}.json \
67+
-v $GOOGLE_APPLICATION_CREDENTIALS:/tmp/keys/${SA_KEY_NAME}.json:ro \
68+
-v $PWD:/app $SAMPLE
69+
```
70+
71+
## Deploying
72+
73+
```
74+
gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/${SAMPLE}
75+
gcloud beta run deploy $SAMPLE \
76+
# Needed for Manual Logging sample.
77+
--set-env-var GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT}
78+
--image gcr.io/${GOOGLE_CLOUD_PROJECT}/${SAMPLE}
79+
```
80+
81+
See [Building containers][run_build] and [Deploying container images][run_deploy]
82+
for more information.
83+
84+
[run_docs]: https://cloud.google.com/run/docs/
85+
[run_build]: https://cloud.google.com/run/docs/building/containers
86+
[run_deploy]: https://cloud.google.com/run/docs/deploying
87+
[helloworld]: https://github.com/knative/docs/tree/master/docs/serving/samples/hello-world/helloworld-python
88+
[pubsub]: pubsub/
89+
[run_button_helloworld]: https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_image=gcr.io/cloudrun/button&cloudshell_git_repo=https://github.com/knative/docs&cloudshell_working_dir=docs/serving/samples/hello-world/helloworld-python
90+
[run_button_pubsub]: https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_image=gcr.io/cloudrun/button&cloudshell_git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&cloudshell_working_dir=run/pubsub

run/pubsub/.dockerignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Dockerfile
2+
.dockerignore
3+
__pycache__
4+
.pytest_cache

run/pubsub/.gcloudignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.pytest_cache
2+
__pycache__

run/pubsub/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__pycache__
2+
.pytest_cache

run/pubsub/Dockerfile

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright 2019 Google, LLC.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# [START run_pubsub_dockerfile]
16+
17+
# Use the official Python image.
18+
# https://hub.docker.com/_/python
19+
FROM python:3.7
20+
21+
# Copy application dependency manifests to the container image.
22+
# Copying this separately prevents re-running pip install on every code change.
23+
COPY requirements.txt ./
24+
25+
# Install production dependencies.
26+
RUN pip install -r requirements.txt
27+
28+
# Copy local code to the container image.
29+
ENV APP_HOME /app
30+
WORKDIR $APP_HOME
31+
COPY . .
32+
33+
# Run the web service on container startup. Here we use the gunicorn
34+
# webserver, with one worker process and 8 threads.
35+
# For environments with multiple CPU cores, increase the number of workers
36+
# to be equal to the cores available.
37+
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 main:app
38+
39+
# [END run_pubsub_dockerfile]

run/pubsub/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Cloud Run Pub/Sub Tutorial Sample
2+
3+
This sample shows how to create a service that processes Pub/Sub messages.
4+
5+
Use it with the [Cloud Pub/Sub with Cloud Run tutorial](http://cloud.google.com/run/docs/tutorials/pubsub).
6+
7+
## Build
8+
9+
```
10+
docker build --tag pubsub-tutorial:python .
11+
```
12+
13+
## Run Locally
14+
15+
```
16+
docker run --rm -p 9090:8080 pubsub-tutorial:python
17+
```
18+
19+
## Test
20+
21+
```
22+
pytest
23+
```
24+
25+
_Note: you may need to install `pytest` using `pip install pytest`._
26+
27+
## Deploy
28+
29+
```
30+
gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/pubsub-tutorial
31+
gcloud alpha run deploy pubsub-tutorial --image gcr.io/${GOOGLE_CLOUD_PROJECT}/pubsub-tutorial
32+
```

run/pubsub/main.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Copyright 2019 Google, LLC.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# [START run_pubsub_server_setup]
16+
import base64
17+
from flask import Flask, request
18+
import os
19+
20+
app = Flask(__name__)
21+
# [END run_pubsub_server_setup]
22+
23+
24+
# [START run_pubsub_handler]
25+
@app.route('/', methods=['POST'])
26+
def index():
27+
envelope = request.get_json()
28+
if not envelope:
29+
msg = 'no Pub/Sub message received'
30+
print(f'error: {msg}')
31+
return f'Bad Request: {msg}', 400
32+
33+
if not isinstance(envelope, dict) or 'message' not in envelope:
34+
msg = 'invalid Pub/Sub message format'
35+
print(f'error: {msg}')
36+
return f'Bad Request: {msg}', 400
37+
38+
pubsub_message = envelope['message']
39+
40+
name = 'World'
41+
if isinstance(pubsub_message, dict) and 'data' in pubsub_message:
42+
name = base64.b64decode(pubsub_message['data']).decode('utf-8').strip()
43+
44+
print(f'Hello {name}!')
45+
return ('', 204)
46+
# [END run_pubsub_handler]
47+
48+
49+
if __name__ == '__main__':
50+
PORT = int(os.getenv('PORT')) if os.getenv('PORT') else 8080
51+
52+
# This is used when running locally. Gunicorn is used to run the
53+
# application on Cloud Run. See entrypoint in Dockerfile.
54+
app.run(host='127.0.0.1', port=PORT, debug=True)

run/pubsub/main_test.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copyright 2019 Google, LLC.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# NOTE:
16+
# These tests are unit tests that mock Pub/Sub.
17+
18+
import base64
19+
from uuid import uuid4
20+
21+
import pytest
22+
23+
import main
24+
25+
26+
@pytest.fixture
27+
def client():
28+
main.app.testing = True
29+
return main.app.test_client()
30+
31+
32+
def test_empty_payload(client):
33+
r = client.post('/', json='')
34+
assert r.status_code == 400
35+
36+
37+
def test_invalid_payload(client):
38+
r = client.post('/', json={'nomessage': 'invalid'})
39+
assert r.status_code == 400
40+
41+
42+
def test_invalid_mimetype(client):
43+
r = client.post('/', json="{ message: true }")
44+
assert r.status_code == 400
45+
46+
47+
def test_minimally_valid_message(client, capsys):
48+
r = client.post('/', json={'message': True})
49+
assert r.status_code == 204
50+
51+
out, _ = capsys.readouterr()
52+
assert 'Hello World!' in out
53+
54+
55+
def test_populated_message(client, capsys):
56+
name = str(uuid4())
57+
data = base64.b64encode(name.encode()).decode()
58+
59+
r = client.post('/', json={'message': {'data': data}})
60+
assert r.status_code == 204
61+
62+
out, _ = capsys.readouterr()
63+
assert f'Hello {name}!' in out

run/pubsub/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Flask==1.0.2
2+
pytest==4.3.1
3+
gunicorn==19.9.0

0 commit comments

Comments
 (0)