forked from pulumi/examples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Hackathon ETA service - GCP python function (pulumi#274)
* Add gcp python fxn example
- Loading branch information
Erin Krengel
committed
Apr 8, 2019
1 parent
a61e35a
commit ec43670
Showing
7 changed files
with
296 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.pyc | ||
venv/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}}` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
twilio>=6.26.0 | ||
googlemaps>=3.0.2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |