-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cloud Task + GAE + Functions Tutorial #1488
Changes from 19 commits
aad4ff6
5b0ef75
5ce9b69
9e7d19e
6c3fedc
74a300d
a90062e
1d22519
9bb9e1f
2c2187b
6477d03
acf4a1e
73d7ab3
95e7ac4
f0eb573
b5e103a
176961b
cadeddd
efee1ab
cfbc82b
fe94dbd
4202798
be4588e
3446705
a0b74b1
4e8f7ea
5400912
3d0f6af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Format: //devtools/kokoro/config/proto/build.proto | ||
|
||
# Set the folder in which the tests are run | ||
env_vars: { | ||
key: "PROJECT" | ||
value: "cloud-tasks/app" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Format: //devtools/kokoro/config/proto/build.proto | ||
|
||
# Set the folder in which the tests are run | ||
env_vars: { | ||
key: "PROJECT" | ||
value: "cloud-tasks/function" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Format: //devtools/kokoro/config/proto/build.proto | ||
|
||
# Download trampoline resources. These will be in ${KOKORO_GFILE_DIR} | ||
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" | ||
|
||
# Download secrets from Cloud Storage. | ||
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/nodejs-docs-samples" | ||
|
||
# All builds use the trampoline script to run in docker. | ||
build_file: "nodejs-docs-samples/.kokoro/trampoline.sh" | ||
|
||
# Tell the trampoline which build file to use. | ||
env_vars: { | ||
key: "TRAMPOLINE_BUILD_FILE" | ||
value: "github/nodejs-docs-samples/.kokoro/build.sh" | ||
} | ||
|
||
# Configure the docker image for kokoro-trampoline. | ||
env_vars: { | ||
key: "TRAMPOLINE_IMAGE" | ||
value: "gcr.io/cloud-devrel-kokoro-resources/node:10-user" | ||
} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# [Node.js Cloud Tasks sample for Google App Engine and Cloud Functions][tutorial-link] | ||
|
||
This is the sample application for the | ||
[Using Cloud Tasks to trigger Cloud Functions][tutorial-link] tutorial. | ||
|
||
This tutorial shows how to create [Cloud Tasks][cloud-tasks] on | ||
[Google App Engine Standard][gae-std] to trigger a [Cloud Function][cloud-func] | ||
in order to send a postcard email. | ||
|
||
## Application Architecture | ||
|
||
* The App Engine application calls the Cloud Tasks API to add a scheduled task | ||
to the queue. | ||
|
||
* The queue processes tasks and sends requests to a Cloud Function. | ||
|
||
* The Cloud Function calls the SendGrid API to send a postcard email. | ||
|
||
[tutorial-link]: https://cloud.google.com/tasks/docs/tutorial-gcf | ||
[cloud-tasks]: https://cloud.google.com/tasks/docs/ | ||
[gae-std]: https://cloud.google.com/appengine/docs/standard/nodejs/ | ||
[cloud-func]: https://cloud.google.com/functions/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# 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 | ||
# | ||
# https://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. | ||
|
||
runtime: nodejs10 | ||
|
||
# [START cloud_tasks_app_env_vars] | ||
env_variables: | ||
QUEUE_NAME: "my-queue" | ||
QUEUE_LOCATION: "us-central1" | ||
FUNCTION_URL: "https://<region>-<project_id>.cloudfunctions.net/sendPostcard" | ||
SERVICE_ACCOUNT_EMAIL: "<member>@<project_id>.iam.gserviceaccount.com" | ||
# [END cloud_tasks_app_env_vars] | ||
|
||
handlers: | ||
- url: / | ||
static_files: static/index.html | ||
upload: static/index.html | ||
- url: /static | ||
static_dir: static | ||
averikitsch marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/** | ||
* 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 strict'; | ||
|
||
// [START cloud_tasks_app_create_task] | ||
const MAX_SCHEDULE_LIMIT = 30 * 60 * 60 * 24; // Represents 30 days in seconds. | ||
|
||
async function createHttpTaskWithToken( | ||
project = 'my-project-id', // Your GCP Project id | ||
queue = 'my-queue', // Name of your Queue | ||
location = 'us-central1', // The GCP region of your queue | ||
url = 'https://example.com/taskhandler', // The full url path that the request will be sent to | ||
email = '<member>@<project-id>.iam.gserviceaccount.com', // Cloud IAM service account | ||
payload = 'Hello, World!', // The task HTTP request body | ||
date = new Date() // Intended date to schedule task | ||
) { | ||
// Imports the Google Cloud Tasks library. | ||
const {v2beta3} = require('@google-cloud/tasks'); | ||
|
||
// Instantiates a client. | ||
const client = new v2beta3.CloudTasksClient(); | ||
|
||
// Construct the fully qualified queue name. | ||
const parent = client.queuePath(project, location, queue); | ||
|
||
// Convert message to buffer | ||
const convertedPayload = JSON.stringify(payload); | ||
const body = Buffer.from(convertedPayload).toString('base64'); | ||
|
||
const task = { | ||
httpRequest: { | ||
httpMethod: 'POST', | ||
url, | ||
oidcToken: { | ||
serviceAccountEmail: email, | ||
}, | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body, | ||
}, | ||
}; | ||
|
||
const convertedDate = new Date(date); | ||
const currentDate = new Date(); | ||
|
||
// Schedule time can not be in the past | ||
if (convertedDate < currentDate) { | ||
console.error('Scheduled date in the past.'); | ||
} else if (convertedDate > currentDate) { | ||
// Restrict schedule time to the 30 day maximum | ||
const date_diff_in_seconds = (convertedDate - currentDate) / 1000; | ||
if (date_diff_in_seconds > MAX_SCHEDULE_LIMIT) { | ||
console.error('Schedule time is over 30 day maximum.'); | ||
} | ||
const date_in_seconds = | ||
Math.min(date_diff_in_seconds, MAX_SCHEDULE_LIMIT) + Date.now() / 1000; | ||
// Add schedule time to request in Timestamp format | ||
// https://googleapis.dev/nodejs/tasks/latest/google.protobuf.html#.Timestamp | ||
task.scheduleTime = { | ||
seconds: date_in_seconds, | ||
averikitsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
} | ||
|
||
try { | ||
// Send create task request. | ||
const [response] = await client.createTask({parent, task}); | ||
console.log(`Created task ${response.name}`); | ||
return response.name; | ||
} catch (error) { | ||
// Construct error for Stackdriver Error Reporting | ||
console.error(Error(error.message)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-Blocking. Did you end up confirming that It would be worth comparing the output from the two commands and seeing what the difference is, it might be fixable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did run some more isolated tests and |
||
} | ||
} | ||
|
||
module.exports = createHttpTaskWithToken; | ||
// [END cloud_tasks_app_create_task] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/** | ||
* 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 strict'; | ||
|
||
const createHttpTaskWithToken = require('./createTask'); | ||
const express = require('express'); | ||
|
||
const app = express(); | ||
|
||
const {QUEUE_NAME} = process.env; | ||
const {QUEUE_LOCATION} = process.env; | ||
const {FUNCTION_URL} = process.env; | ||
const {SERVICE_ACCOUNT_EMAIL} = process.env; | ||
|
||
app.use(express.urlencoded({extended: true})); | ||
averikitsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// [START cloud_tasks_app] | ||
app.post('/send-email', (req, res) => { | ||
// Set the task payload to the form submission. | ||
const {to_name, from_name, to_email, date} = req.body; | ||
const payload = {to_name, from_name, to_email}; | ||
|
||
createHttpTaskWithToken( | ||
process.env.GOOGLE_CLOUD_PROJECT, | ||
QUEUE_NAME, | ||
QUEUE_LOCATION, | ||
FUNCTION_URL, | ||
SERVICE_ACCOUNT_EMAIL, | ||
payload, | ||
date | ||
); | ||
|
||
res.status(202).send('📫 Your postcard is in the mail! 💌'); | ||
}); | ||
// [END cloud_tasks_app] | ||
|
||
app.get('*', (req, res) => { | ||
res.send('OK').end(); | ||
}); | ||
averikitsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const PORT = process.env.PORT || 8080; | ||
app.listen(PORT, () => { | ||
console.log(`App listening on port ${PORT}`); | ||
console.log('Press Ctrl+C to quit.'); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"name": "cloud-tasks-app", | ||
"description": "Creates Cloud Tasks from form submission", | ||
"main": "index.js", | ||
"private": true, | ||
"scripts": { | ||
"start": "node index.js", | ||
"test": "mocha" | ||
}, | ||
"dependencies": { | ||
"@google-cloud/tasks": "^1.2.1", | ||
"express": "^4.17.1" | ||
}, | ||
"devDependencies": { | ||
"chai": "^4.2.0", | ||
"mocha": "^6.1.3" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
.postcard { | ||
margin: auto; | ||
text-align: center; | ||
position: relative; | ||
width: 600px; | ||
height: 400px; | ||
background: #4285F4; | ||
box-shadow: 0px 7px 20px 0px rgba(0, 0, 0, 0.5); | ||
} | ||
|
||
.postcard::after { | ||
content: ''; | ||
position: absolute; | ||
left: 30px; | ||
top: 30px; | ||
bottom: 30px; | ||
right: 30px; | ||
border: 5px solid #FFF; | ||
} | ||
|
||
.postcard-text { | ||
font-family: Arial, sans-serif; | ||
font-size: 60px; | ||
font-weight: bold; | ||
text-transform: uppercase; | ||
color: #FFF; | ||
background: #4285F4; | ||
position: absolute; | ||
left: 0; | ||
padding-left: 27px; | ||
bottom: 0; | ||
padding-bottom: 15px; | ||
z-index: 2; | ||
} | ||
|
||
.postcard-names { | ||
font-family: Monaco, monospace; | ||
font-size: 40px; | ||
text-align: right; | ||
color: #FFF; | ||
background: #4285F4; | ||
position: absolute; | ||
right: 0; | ||
top: 0; | ||
padding: 15px 27px; | ||
z-index: 2; | ||
max-width: 546px; | ||
max-height: 300px; | ||
overflow: hidden; | ||
} | ||
|
||
.info { | ||
margin: auto; | ||
text-align: center; | ||
position: relative; | ||
margin-top: 3em; | ||
font-family: Arial, sans-serif; | ||
} | ||
|
||
.info-line { | ||
margin: 1em; | ||
} | ||
|
||
.postcard-names input { | ||
font-family: Monaco, monospace; | ||
font-size: 36px; | ||
color: #FFF; | ||
background: #4285F4; | ||
border-bottom: 3px solid #FFF; | ||
width: 200px; | ||
} | ||
|
||
.info-line input, label { | ||
font-family: Monaco, monospace; | ||
font-size: 24px; | ||
} | ||
|
||
button { | ||
font-family: Monaco, monospace; | ||
font-size: 24px; | ||
color: #FFF; | ||
text-transform: uppercase; | ||
background: #4285F4; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does cloud-tasks need a customized common.cfg? So far most products don't need one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every folder in
.kokoro
has acommon.cfg
file, but you are right it is a duplication of the parents.