Skip to content

Commit

Permalink
Hackathon ETA service - GCP python function (pulumi#274)
Browse files Browse the repository at this point in the history
* Add gcp python fxn example
  • Loading branch information
Erin Krengel committed Apr 8, 2019
1 parent a61e35a commit ec43670
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 0 deletions.
2 changes: 2 additions & 0 deletions gcp-py-functions/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.pyc
venv/
3 changes: 3 additions & 0 deletions gcp-py-functions/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: gcp-py-functions
runtime: python
description: A minimal Python Pulumi program using Google Cloud Functions.
136 changes: 136 additions & 0 deletions gcp-py-functions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)

# GCP Functions

This example shows how to deploy a Python-based Google Cloud Function.

The deployed Cloud Function allows you to notify a friend via SMS about how long it will take to
arrive at a location. This uses the Google Maps API and Twilio, and also benefits from using a
[Flic button](https://flic.io) and [IFTTT](https://ifttt.com). But none of that is necessary to
use Pulumi to provision the Google Cloud Platform resources.

## Creating the Stack

1. Create a new stack:

```bash
pulumi stack init gcp-py-functions
```

1. Configure GCP project and region:

```bash
pulumi config set gcp:project <projectname>
pulumi config set gcp:region <region>
```

1. Download dependencies:

```bash
# (Optional) Create a virtualenv environment.
virtualenv -p python3 venv
source venv/bin/activate

pip3 install -r requirements.txt
```

1. Run `pulumi up` to preview and deploy changes:

```bash
$ pulumi up
Previewing changes:
...

Performing changes:
...
Resources:
+ 4 created
Duration: 1m2s
```

Once the application is deployed, you can start accessing the Google Cloud Function by making an
HTTP request to the function's endpoint. It is exported from the stack's as `fxn_url`.

```bash
$ pulumi stack output fxn_url
https://us-central1-pulumi-gcp-dev.cloudfunctions.net/eta_demo_function...
```

You can specify a starting location via latitude and longitude coordinates via URL query
parameters. (You can find your current location on [https://www.latlong.net/](https://www.latlong.net/).)

```bash
$ curl "$(pulumi stack output fxn_url)?lat=<YOUR_LATITUDE>&long=<YOUR_LONGITUDE>"
Sent text message to...
```

## Configuration

### Google Maps for Travel Time

The application uses the [Google Maps API](https://developers.google.com/maps/documentation/) to estimate travel time. To set this up:

1. Get a [Google Maps](https://cloud.google.com/maps-platform/) API key by clicking 'Get started'.

* Check the Routes and then click continue.
* Select the GCP project you are deploying your Cloud function to.

1. Update the stack's configuration, encrypting the API key value:

```bash
pulumi config set googleMapsApiKey <INSERT_API_KEY> --secret
```

1. Set the target destination to compute directions to:

```bash
pulumi config set destination <DESTINATION>
```

1. (Optional) Add a travel time offset, e.g. add 5 minutes to the estimate.

```bash
pulumi config set travelOffset <TRAVEL_OFFSET>
```

1. Run `pulumi up` to re-deploy your cloud function with the new configuration.

### Twilio for SMS Notifications

To have the Cloud Function send a text message, you'll need to a Twilio key too:

1. Log into your [Twilio](https://www.twilio.com/) account, and create a new access token
and/or phone number to send SMS messages from.

1. Add the Twilio configuration data to your Pulumi stack:

```bash
pulumi config set twillioAccessToken <TWILIO_ACCESS_TOKEN> --secret
pulumi config set twillioAccountSid <TWILIO_ACCOUNT_SID> --secret
pulumi config set fromPhoneNumber <FROM_PHONE_NUMBER>
```

1. Enter the phone number the Cloud Function will send messages to:

```bash
pulumi config set toPhoneNumber <TO_PHONE_NUMBER> --secret
```

1. Run `pulumi up` to re-deploy your cloud function with the new configuration.

### Flic Button to Trigger the Cloud Function

With Pulumi having setup the cloud infrastructure, the next step is to have a simple way to trigger
it. With [Flic](https://flic.io) you can trigger the Cloud Function with literally the push
of a button.

To make sure to include the button presser's location, you can use [IFTTT](https://ifttt.com).

1. Install the Flic app on your phone and pair your button. Enable location services for the Flic app
and add an IFTTT for one of the click gestures.

1. Create a new Applet on IFTTT: "If You click a Flic, then Make a web request"
* For "If" select the "Flic" service then "Flic is clicked".
* Select your Flic button and the appropriate gesture from the menu.
* For "Then" select the "Make a web request" service
* Under URL enter following (replace `<FUNCTION_URL>` with the value from `pulumi stack output fxn_url`): `<FUNCTION_URL>?long={{Longitude}}&lat={{Latitude}}`
72 changes: 72 additions & 0 deletions gcp-py-functions/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Python Pulumi program for creating Google Cloud Functions.
Create a single Google Cloud Function. The deployed application will calculate
the estimated travel time to a given location, sending the results via SMS.
"""

import time
import os
import pulumi

from pulumi_gcp import storage
from pulumi_gcp import cloudfunctions

# Disable rule for that module-level exports be ALL_CAPS, for legibility.
# pylint: disable=C0103

# File path to where the Cloud Function's source code is located.
PATH_TO_SOURCE_CODE = "./functions"

# Get values from Pulumi config to use as environment variables in our Cloud Function.
config = pulumi.Config(name=None)
config_values = {
# Target destination and travel time offset.
"DESTINATION": config.get("destination"),
"TRAVEL_OFFSET": config.get("travelOffset"),

# Google Maps API key.
"GOOGLE_MAPS_API_KEY": config.get("googleMapsApiKey"),

# Twilio account for sending SMS messages.
"TWILLIO_ACCESS_TOKEN": config.get("twillioAccessToken"),
"TWILLIO_ACCOUNT_SID": config.get("twillioAccountSid"),
"TO_PHONE_NUMBER": config.get("toPhoneNumber"),
"FROM_PHONE_NUMBER": config.get("fromPhoneNumber"),
}

# We will store the source code to the Cloud Function in a Google Cloud Storage bucket.
bucket = storage.Bucket("eta_demo_bucket")

# The Cloud Function source code itself needs to be zipped up into an
# archive, which we create using the pulumi.AssetArchive primitive.
assets = {}
for file in os.listdir(PATH_TO_SOURCE_CODE):
location = os.path.join(PATH_TO_SOURCE_CODE, file)
asset = pulumi.FileAsset(path=location)
assets[file] = asset

archive = pulumi.AssetArchive(assets=assets)

# Create the single Cloud Storage object, which contains all of the function's
# source code. ("main.py" and "requirements.txt".)
source_archive_object = storage.BucketObject(
"eta_demo_object",
name="main.py-%f" % time.time(),
bucket=bucket.name,
source=archive)

# Create the Cloud Function, deploying the source we just uploaded to Google
# Cloud Storage.
fxn = cloudfunctions.Function(
"eta_demo_function",
entry_point="get_demo",
environment_variables=config_values,
region="us-central1",
runtime="python37",
source_archive_bucket=bucket.name,
source_archive_object=source_archive_object.name,
trigger_http=True)

# Export the DNS name of the bucket and the cloud function URL.
pulumi.export("bucket_name", bucket.url)
pulumi.export("fxn_url", fxn.https_trigger_url)
77 changes: 77 additions & 0 deletions gcp-py-functions/functions/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Google Cloud Function source code for an ETA messaging app.
Defines a single Cloud Function endpoint, get_demo, which will compute the
estimated travel time to a location. If configured, will also send the result
via SMS.
"""

import os
from datetime import datetime
import googlemaps
import twilio.rest


def get_travel_time(origin, destination, offset):
"""Returns the estimated travel time using the Google Maps API.
Returns: A string, such as '3 minutes'"""
key = os.getenv("GOOGLE_MAPS_API_KEY", "")
if key == "":
return "[ENABLE GOOGLE MAPS TO DETERMINE TRAVEL TIME]"

gmaps = googlemaps.Client(key=key)
now = datetime.now()
directions_result = gmaps.directions(
origin=origin,
destination=destination,
mode="driving",
departure_time=now)

travel_time = directions_result[0]["legs"][0]["duration"]["value"]
travel_time /= 60 # seconds to minutes
travel_time += offset

return "%d minutes" % travel_time

def send_text(message_body):
"""Sends an SMS using the Twilio API."""
to_number = os.getenv("TO_PHONE_NUMBER", "")
from_number = os.getenv("FROM_PHONE_NUMBER", "")
account_sid = os.getenv("TWILLIO_ACCOUNT_SID", "")
auth_token = os.getenv("TWILLIO_ACCESS_TOKEN", "")

if account_sid and auth_token and to_number and from_number:
client = twilio.rest.Client(account_sid, auth_token)
client.messages.create(
to=to_number,
from_=from_number,
body=message_body)
return "Sent text message to %s\n%s" % (to_number, message_body)
return "[ENABLE TWILIO TO SEND A TEXT]: \n%s" % (message_body)

def get_demo(request):
"""The Google Cloud Function computing estimated travel time."""

# Get origin location from URL-query parameters.
lat = request.args.get("lat")
long = request.args.get("long")
if lat and long:
origin = "%s, %s" % (lat, long)
else:
origin = "Pulumi HQ, Seattle, WA"

destination = os.getenv(
"DESTINATION",
"Space Needle, Seattle, WA")

# Optional travel time offset, e.g. add a static 5m delay.
travel_offset_str = os.getenv("TRAVEL_OFFSET", "0")
travel_offset = int(travel_offset_str)

travel_time_str = get_travel_time(
origin=origin, destination=destination, offset=travel_offset)

# Send the message. Returns a summary in the Cloud Function's response.
message = "Hey! I'm leaving now, I'll be at '%s' to pick you up in about %s." % (
destination, travel_time_str)
return send_text(message)
2 changes: 2 additions & 0 deletions gcp-py-functions/functions/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
twilio>=6.26.0
googlemaps>=3.0.2
4 changes: 4 additions & 0 deletions gcp-py-functions/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pulumi>=0.16.4
pulumi_gcp>=0.16.2
twilio>=6.26.0
googlemaps>=3.0.2

0 comments on commit ec43670

Please sign in to comment.