Skip to content

Commit 12d9345

Browse files
authored
Cloud Task + GAE + Functions Tutorial (#1488)
* add samples and tests * Fix kokoro typo * linting * remove comment * lint * Update service account * Update app * update tag and readme * remove version * Simplify readme * Update param comments * clean up * remove kokoro common file duplicate * Update comments and tests * Add region tag * g/postcard/email/ * re-add common file * lint
1 parent 42e9f34 commit 12d9345

14 files changed

Lines changed: 752 additions & 0 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Format: //devtools/kokoro/config/proto/build.proto
2+
3+
# Set the folder in which the tests are run
4+
env_vars: {
5+
key: "PROJECT"
6+
value: "cloud-tasks/app"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Format: //devtools/kokoro/config/proto/build.proto
2+
3+
# Set the folder in which the tests are run
4+
env_vars: {
5+
key: "PROJECT"
6+
value: "cloud-tasks/function"
7+
}

.kokoro/cloud-tasks/common.cfg

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Format: //devtools/kokoro/config/proto/build.proto
2+
3+
# Download trampoline resources. These will be in ${KOKORO_GFILE_DIR}
4+
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
5+
6+
# Download secrets from Cloud Storage.
7+
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/nodejs-docs-samples"
8+
9+
# All builds use the trampoline script to run in docker.
10+
build_file: "nodejs-docs-samples/.kokoro/trampoline.sh"
11+
12+
# Tell the trampoline which build file to use.
13+
env_vars: {
14+
key: "TRAMPOLINE_BUILD_FILE"
15+
value: "github/nodejs-docs-samples/.kokoro/build.sh"
16+
}
17+
18+
# Configure the docker image for kokoro-trampoline.
19+
env_vars: {
20+
key: "TRAMPOLINE_IMAGE"
21+
value: "gcr.io/cloud-devrel-kokoro-resources/node:10-user"
22+
}

cloud-tasks/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# [Node.js Cloud Tasks sample for Google App Engine and Cloud Functions][tutorial-link]
2+
3+
This is the sample application for the
4+
[Using Cloud Tasks to trigger Cloud Functions][tutorial-link] tutorial.
5+
6+
This tutorial shows how to create [Cloud Tasks][cloud-tasks] on
7+
[Google App Engine Standard][gae-std] to trigger a [Cloud Function][cloud-func]
8+
in order to send a postcard email.
9+
10+
## Application Architecture
11+
12+
* The App Engine application calls the Cloud Tasks API to add a scheduled task
13+
to the queue.
14+
15+
* The queue processes tasks and sends requests to a Cloud Function.
16+
17+
* The Cloud Function calls the SendGrid API to send a postcard email.
18+
19+
[tutorial-link]: https://cloud.google.com/tasks/docs/tutorial-gcf
20+
[cloud-tasks]: https://cloud.google.com/tasks/docs/
21+
[gae-std]: https://cloud.google.com/appengine/docs/standard/nodejs/
22+
[cloud-func]: https://cloud.google.com/functions/

cloud-tasks/app/app.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
# https://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 cloud_tasks_app_yaml]
16+
runtime: nodejs10
17+
18+
# [START cloud_tasks_app_env_vars]
19+
env_variables:
20+
QUEUE_NAME: "my-queue"
21+
QUEUE_LOCATION: "us-central1"
22+
FUNCTION_URL: "https://<region>-<project_id>.cloudfunctions.net/sendEmail"
23+
SERVICE_ACCOUNT_EMAIL: "<member>@<project_id>.iam.gserviceaccount.com"
24+
# [END cloud_tasks_app_env_vars]
25+
26+
# Handlers for serving the index page.
27+
handlers:
28+
- url: /static
29+
static_dir: static
30+
- url: /
31+
static_files: index.html
32+
upload: index.html
33+
# [END cloud_tasks_app_yaml]

cloud-tasks/app/createTask.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* Copyright 2019 Google LLC
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+
16+
'use strict';
17+
18+
// [START cloud_tasks_app_create_task]
19+
const MAX_SCHEDULE_LIMIT = 30 * 60 * 60 * 24; // Represents 30 days in seconds.
20+
21+
const createHttpTaskWithToken = async function(
22+
project = 'my-project-id', // Your GCP Project id
23+
queue = 'my-queue', // Name of your Queue
24+
location = 'us-central1', // The GCP region of your queue
25+
url = 'https://example.com/taskhandler', // The full url path that the request will be sent to
26+
email = '<member>@<project-id>.iam.gserviceaccount.com', // Cloud IAM service account
27+
payload = 'Hello, World!', // The task HTTP request body
28+
date = new Date() // Intended date to schedule task
29+
) {
30+
// Imports the Google Cloud Tasks library.
31+
const {v2beta3} = require('@google-cloud/tasks');
32+
33+
// Instantiates a client.
34+
const client = new v2beta3.CloudTasksClient();
35+
36+
// Construct the fully qualified queue name.
37+
const parent = client.queuePath(project, location, queue);
38+
39+
// Convert message to buffer.
40+
const convertedPayload = JSON.stringify(payload);
41+
const body = Buffer.from(convertedPayload).toString('base64');
42+
43+
const task = {
44+
httpRequest: {
45+
httpMethod: 'POST',
46+
url,
47+
oidcToken: {
48+
serviceAccountEmail: email,
49+
},
50+
headers: {
51+
'Content-Type': 'application/json',
52+
},
53+
body,
54+
},
55+
};
56+
57+
const convertedDate = new Date(date);
58+
const currentDate = new Date();
59+
60+
// Schedule time can not be in the past.
61+
if (convertedDate < currentDate) {
62+
console.error('Scheduled date in the past.');
63+
} else if (convertedDate > currentDate) {
64+
const date_diff_in_seconds = (convertedDate - currentDate) / 1000;
65+
// Restrict schedule time to the 30 day maximum.
66+
if (date_diff_in_seconds > MAX_SCHEDULE_LIMIT) {
67+
console.error('Schedule time is over 30 day maximum.');
68+
}
69+
// Construct future date in Unix time.
70+
const date_in_seconds =
71+
Math.min(date_diff_in_seconds, MAX_SCHEDULE_LIMIT) + Date.now() / 1000;
72+
// Add schedule time to request in Unix time using Timestamp structure.
73+
// https://googleapis.dev/nodejs/tasks/latest/google.protobuf.html#.Timestamp
74+
task.scheduleTime = {
75+
seconds: date_in_seconds,
76+
};
77+
}
78+
79+
try {
80+
// Send create task request.
81+
const [response] = await client.createTask({parent, task});
82+
console.log(`Created task ${response.name}`);
83+
return response.name;
84+
} catch (error) {
85+
// Construct error for Stackdriver Error Reporting
86+
console.error(Error(error.message));
87+
}
88+
};
89+
90+
module.exports = createHttpTaskWithToken;
91+
// [END cloud_tasks_app_create_task]

cloud-tasks/app/index.html

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!DOCTYPE html>
2+
<!-- Copyright 2019 Google LLC
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+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License. -->
12+
<html lang="en">
13+
<head>
14+
<meta charset="UTF-8">
15+
<title>Send a Postcard</title>
16+
<link rel="stylesheet" href="static/index.css">
17+
</head>
18+
<body class="wrapper">
19+
<form action="/send-email" method="post">
20+
<div class="postcard">
21+
<div class="postcard-names">
22+
To:<input type="text" name="to_name" required></input>
23+
<br>
24+
From:<input type="text" name="from_name" required></input>
25+
</div>
26+
<div class="postcard-text">
27+
Hello,<br>World!
28+
</div>
29+
</div>
30+
31+
<div class="info">
32+
<div class="info-line">
33+
<label for="to_email">Recipient's Email</label>
34+
<input type="text" id="to_email" name="to_email" required></input>
35+
</div>
36+
<div class="info-line">
37+
<label for="date">Schedule Postcard</label>
38+
<input id="date-input" type="date" name="date">
39+
</div>
40+
<button>Send</button>
41+
</div>
42+
</form>
43+
44+
<script>
45+
// Restrict date selector to dates: now - now + 30 days
46+
const now = new Date();
47+
const maxDate = new Date(now);
48+
maxDate.setDate(now.getDate() + 30);
49+
50+
document.getElementById('date-input')
51+
.setAttribute('min', now.toISOString().substring(0, 10));
52+
document.getElementById('date-input')
53+
.setAttribute('max', maxDate.toISOString().substring(0, 10));
54+
</script>
55+
</body>
56+
</html>

cloud-tasks/app/index.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Copyright 2019 Google LLC
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+
16+
'use strict';
17+
18+
const createHttpTaskWithToken = require('./createTask');
19+
const express = require('express');
20+
21+
const app = express();
22+
23+
const {QUEUE_NAME} = process.env;
24+
const {QUEUE_LOCATION} = process.env;
25+
const {FUNCTION_URL} = process.env;
26+
const {SERVICE_ACCOUNT_EMAIL} = process.env;
27+
28+
// Parse form inputs from /index.html.
29+
app.use(express.urlencoded({extended: true}));
30+
31+
// [START cloud_tasks_app]
32+
app.post('/send-email', (req, res) => {
33+
// Set the task payload to the form submission.
34+
const {to_name, from_name, to_email, date} = req.body;
35+
const payload = {to_name, from_name, to_email};
36+
37+
createHttpTaskWithToken(
38+
process.env.GOOGLE_CLOUD_PROJECT,
39+
QUEUE_NAME,
40+
QUEUE_LOCATION,
41+
FUNCTION_URL,
42+
SERVICE_ACCOUNT_EMAIL,
43+
payload,
44+
date
45+
);
46+
47+
res.status(202).send('📫 Your postcard is in the mail! 💌');
48+
});
49+
// [END cloud_tasks_app]
50+
51+
const PORT = process.env.PORT || 8080;
52+
app.listen(PORT, () => {
53+
console.log(`App listening on port ${PORT}`);
54+
console.log('Press Ctrl+C to quit.');
55+
});

cloud-tasks/app/package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "cloud-tasks-app",
3+
"description": "Creates Cloud Tasks from form submission",
4+
"main": "index.js",
5+
"private": true,
6+
"scripts": {
7+
"start": "node index.js",
8+
"test": "mocha"
9+
},
10+
"dependencies": {
11+
"@google-cloud/tasks": "^1.2.1",
12+
"express": "^4.17.1"
13+
},
14+
"devDependencies": {
15+
"chai": "^4.2.0",
16+
"mocha": "^6.1.3"
17+
}
18+
}

0 commit comments

Comments
 (0)