Skip to content

Commit

Permalink
Adding Cloud run system package sample (#2554)
Browse files Browse the repository at this point in the history
  • Loading branch information
dinagraves authored and busunkim96 committed Dec 10, 2019
1 parent cce0c9d commit a09ca74
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 0 deletions.
5 changes: 5 additions & 0 deletions noxfile-template.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ def _setup_appengine_sdk(session):
or str(Path(sample).absolute().relative_to(REPO_ROOT)).startswith(
"bigquery/pandas-gbq-migration"
)
or str(Path(sample).absolute().relative_to(REPO_ROOT)).startswith(
"run/system-package"
)
)
]
NON_GAE_STANDARD_SAMPLES_PY2 = sorted(
Expand Down Expand Up @@ -207,6 +210,7 @@ def py3(session, sample):
"""Runs py.test for a sample using Python 3.x"""
_session_tests(session, sample)


@nox.session(python="3.6")
def lint(session):
session.install("flake8", "flake8-import-order")
Expand All @@ -219,6 +223,7 @@ def lint(session):
]
session.run("flake8", *args)


SAMPLES_WITH_GENERATED_READMES = sorted(list(_collect_dirs(".", suffix=".rst.in")))


Expand Down
1 change: 1 addition & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def _setup_appengine_sdk(session):
sample.startswith("./appengine/standard_python37")
or sample.startswith("./functions/")
or sample.startswith("./bigquery/pandas-gbq-migration")
or sample.startswith("./run/system-package")
)
]
NON_GAE_STANDARD_SAMPLES_PY2 = sorted(
Expand Down
4 changes: 4 additions & 0 deletions run/system-package/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Dockerfile
.dockerignore
__pycache__
.pytest_cache
42 changes: 42 additions & 0 deletions run/system-package/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2019 Google, LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Use the official Python image.
# https://hub.docker.com/_/python
FROM python:3.7

# [START run_system_package_ubuntu]
RUN apt-get update -y && apt-get install -y \
graphviz \
&& apt-get clean
# [END run_system_package_ubuntu]

# Copy application dependency manifests to the container image.
# Copying this separately prevents re-running pip install on every code change.
COPY requirements.txt .

# Install production dependencies.
RUN pip install -r requirements.txt

# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . .

# Run the web service on container startup.
# Use gunicorn webserver with one worker process and 8 threads.
# For environments with multiple CPU cores, increase the number of workers
# to be equal to the cores available.
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 main:app

44 changes: 44 additions & 0 deletions run/system-package/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Cloud Run System Package Sample

This sample shows how to use a CLI tool installed as a system package as part of a web service.

Use it with the [Using system packages tutorial](https://cloud.google.com/run/docs/tutorials/system-packages).


[![Run in Google Cloud][run_img]][run_link]

[run_img]: https://storage.googleapis.com/cloudrun/button.svg
[run_link]: 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/system-package

## Build

```
docker build --tag graphviz:python .
```

## Run Locally

```
docker run --rm -p 9090:8080 graphviz:python
```

## Test

```
pytest
```

_Note: you may need to install `pytest` using `pip install pytest`._

## Deploy

```sh
# Set an environment variable with your GCP Project ID
export GOOGLE_CLOUD_PROJECT=<PROJECT_ID>

# Submit a build using Google Cloud Build
gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/graphviz

# Deploy to Cloud Run
gcloud beta run deploy graphviz --image gcr.io/${GOOGLE_CLOUD_PROJECT}/graphviz
```
85 changes: 85 additions & 0 deletions run/system-package/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Copyright 2019 Google, LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from flask import Flask, make_response, request
import os
import subprocess
import sys


app = Flask(__name__)


# [START run_system_package_handler]
@app.route("/diagram.png", methods=["GET"])
def index():
# Takes an HTTP GET request with query param dot and
# returns a png with the rendered DOT diagram in a HTTP response.
try:
image = create_diagram(request.args.get("dot"))
response = make_response(image)
response.headers.set("Content-Type", "image/png")
return response

except Exception as e:
print("error: {}".format(e))

# Flush the stdout to avoid log buffering.
sys.stdout.flush()

# If no graphviz definition or bad graphviz def, return 400
if "syntax" in str(e):
return "Bad Request: {}".format(e), 400

return "Internal Server Error", 500


# [END run_system_package_handler]


# [START run_system_package_exec]
def create_diagram(dot):
# Generates a diagram based on a graphviz DOT diagram description.
if not dot:
raise Exception("syntax: no graphviz definition provided")

dot_args = [ # These args add a watermark to the dot graphic.
"-Glabel=Made on Cloud Run",
"-Gfontsize=10",
"-Glabeljust=right",
"-Glabelloc=bottom",
"-Gfontcolor=gray",
"-Tpng",
]

# Uses local `dot` binary from Graphviz:
# https://graphviz.gitlab.io
image = subprocess.run(
["dot"] + dot_args, input=dot.encode("utf-8"), stdout=subprocess.PIPE
).stdout

if not image:
raise Exception("syntax: bad graphviz definition provided")
return image


# [END run_system_package_exec]


if __name__ == "__main__":
PORT = int(os.getenv("PORT")) if os.getenv("PORT") else 8080

# This is used when running locally. Gunicorn is used to run the
# application on Cloud Run. See entrypoint in Dockerfile.
app.run(host="127.0.0.1", port=PORT, debug=True)
46 changes: 46 additions & 0 deletions run/system-package/main_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright 2019 Google, LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# NOTE:
# To pass these tests locally, run `brew install graphviz`


import main
import pytest


@pytest.fixture
def client():
main.app.testing = True
return main.app.test_client()


def test_empty_query_string(client):
r = client.get("/diagram.png")
assert r.status_code == 400


def test_empty_dot_parameter(client):
r = client.get("/diagram.png?dot=")
assert r.status_code == 400


def test_bad_dot_parameter(client):
r = client.get("/diagram.png?dot=digraph")
assert r.status_code == 400


def test_good_dot_parameter(client):
r = client.get("/diagram.png?dot=digraph G { A -> {B, C, D} -> {F} }")
assert r.content_type == "image/png"
3 changes: 3 additions & 0 deletions run/system-package/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Flask==1.1.1
pytest==5.1.3
gunicorn==19.9.0

0 comments on commit a09ca74

Please sign in to comment.