Skip to content
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

Migrate Sample: Cloud Run as Pub/Sub Push Handler #1419

Merged
merged 18 commits into from
Jul 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .kokoro/build-with-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export CONTAINER_IMAGE="gcr.io/${GOOGLE_CLOUD_PROJECT}/run-${SAMPLE_NAME}:${SAMP

# Register post-test cleanup.
function cleanup {
gcloud --quiet container images delete "${CONTAINER_IMAGE}"
gcloud --quiet container images delete "${CONTAINER_IMAGE}" || true
}
trap cleanup EXIT

Expand All @@ -53,6 +53,4 @@ set +x
export NODE_ENV=development
npm install
npm test
npm run | grep e2e-test && npm run e2e-test

exit $?
npm run --if-present e2e-test
7 changes: 7 additions & 0 deletions .kokoro/run/pubsub.cfg
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: "run/pubsub"
}
11 changes: 7 additions & 4 deletions run/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

## Samples

| Sample | Description | Deploy |
| ------------------------------- | ------------------------ | ------------- |
|[Hello World][helloworld]&nbsp;&#10149; | Quickstart | [<img src="https://storage.googleapis.com/cloudrun/button.svg" alt="Run on Google Cloud" height="30">][run_button_helloworld] |
|[Manual Logging][manual_logging] | Structured logging without client library | [<img src="https://storage.googleapis.com/cloudrun/button.svg" alt="Run on Google Cloud" height="30">][run_button_manual_logging] |
| Sample | Description | Deploy |
| --------------------------------------- | ------------------------ | ------------- |
|[Hello World][helloworld]&nbsp;&#10149; | Quickstart | [<img src="https://storage.googleapis.com/cloudrun/button.svg" alt="Run on Google Cloud" height="30">][run_button_helloworld] |
|[Manual Logging][manual_logging] | Structured logging without client library | [<img src="https://storage.googleapis.com/cloudrun/button.svg" alt="Run on Google Cloud" height="30">][run_button_manual_logging] |
|[Pub/Sub][pubsub] | Pub/Sub push Handler | [<img src="https://storage.googleapis.com/cloudrun/button.svg" alt="Run on Google Cloud" height="30">][run_button_pubsub] |

For more Cloud Run samples beyond Node.js, see the main list in the [Cloud Run Samples repository](https://github.com/GoogleCloudPlatform/cloud-run-samples).

Expand Down Expand Up @@ -109,5 +110,7 @@ for more information.
[run_deploy]: https://cloud.google.com/run/docs/deploying
[helloworld]: https://github.com/knative/docs/tree/master/docs/serving/samples/hello-world/helloworld-nodejs
[manual_logging]: logging-manual/
[pubsub]: pubsub/
[run_button_helloworld]: https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_image=gcr.io/cloudrun/button&cloudshell_git_repo=https://github.com/knative/docs&cloudshell_working_dir=docs/serving/samples/hello-world/helloworld-nodejs
[run_button_manual_logging]: https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_image=gcr.io/cloudrun/button&cloudshell_git_repo=https://github.com/GoogleCloudPlatform/nodejs-docs-samples&cloudshell_working_dir=run/logging-manual
[run_button_pubsub]: https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_image=gcr.io/cloudrun/button&cloudshell_git_repo=https://github.com/GoogleCloudPlatform/nodejs-docs-samples&cloudshell_working_dir=run/pubsub
6 changes: 4 additions & 2 deletions run/logging-manual/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ WORKDIR /usr/src/app
# Copying this separately prevents re-running npm install on every code change.
COPY package*.json ./

# Install production dependencies.
RUN npm install --only=production
# Install dependencies.
RUN npm install
# For production deploys, add a package-lock.json and use 'npm ci'.
# RUN npm ci --only=production

# Copy local code to the container image.
COPY . .
Expand Down
4 changes: 4 additions & 0 deletions run/pubsub/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
3 changes: 3 additions & 0 deletions run/pubsub/.gcloudignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.gcloudignore
node_modules
npm-debug.log
1 change: 1 addition & 0 deletions run/pubsub/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
30 changes: 30 additions & 0 deletions run/pubsub/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright 2019 Google LLC. All rights reserved.
# Use of this source code is governed by the Apache 2.0
# license that can be found in the LICENSE file.

# [START run_pubsub_dockerfile]

# Use the official Node.js 10 image.
# https://hub.docker.com/_/node
FROM node:10

# Create and change to the app directory.
WORKDIR /usr/src/app

# Copy application dependency manifests to the container image.
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
# Copying this separately prevents re-running npm install on every code change.
COPY package*.json ./

# Install dependencies.
RUN npm install --production
# If you add a package-lock.json, speed your build by switching to 'npm ci'.
# RUN npm ci --only=production

# Copy local code to the container image.
COPY . .

# Run the web service on container startup.
CMD [ "npm", "start" ]

# [END run_pubsub_dockerfile]
13 changes: 13 additions & 0 deletions run/pubsub/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Cloud Run Pub/Sub Tutorial Sample

This sample shows how to create a service that processes Pub/Sub messages.

Use it with the [Cloud Pub/Sub with Cloud Run tutorial](http://cloud.google.com/run/docs/tutorials/pubsub).

For more details on how to work with this sample read the [Google Cloud Run Node.js Samples README](https://github.com/GoogleCloudPlatform/nodejs-docs-samples/run).

## Dependencies

* **express**: Web server framework.
* **body-parser**: express middleware for request payload processing.

40 changes: 40 additions & 0 deletions run/pubsub/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2019 Google LLC. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

// [START run_pubsub_server_setup]
const express = require('express');
const bodyParser = require('body-parser');
const app = express();

app.use(bodyParser.json());
// [END run_pubsub_server_setup]

// [START run_pubsub_handler]
app.post('/', (req, res) => {
if (!req.body) {
const msg = 'no Pub/Sub message received';
console.error(`error: ${msg}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: in an ideal world, lines 19-20 wouldn't be repeated in the clause below (lines 26-27) - but I don't think there's a good way to avoid this repetition here. 🙁

res.status(400).send(`Bad Request: ${msg}`);
return;
}
if (!req.body.message) {
const msg = 'invalid Pub/Sub message format';
console.error(`error: ${msg}`);
res.status(400).send(`Bad Request: ${msg}`);
return;
}

const pubSubMessage = req.body.message;
const name = pubSubMessage.data
? Buffer.from(pubSubMessage.data, 'base64')
.toString()
.trim()
: 'World';

console.log(`Hello ${name}!`);
res.status(204).send();
});
// [END run_pubsub_handler]

module.exports = app;
12 changes: 12 additions & 0 deletions run/pubsub/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2019 Google LLC. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

// [START run_pubsub_server]
const app = require('./app.js');
const PORT = process.env.PORT || 8080;

app.listen(PORT, () =>
console.log(`nodejs-pubsub-tutorial listening on port ${PORT}`)
);
// [END run_pubsub_server]
30 changes: 30 additions & 0 deletions run/pubsub/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "nodejs-pubsub",
"version": "1.0.0",
"private": true,
"description": "Simple Pub/Sub subscriber service sample",
"main": "index.js",
"author": "Google LLC",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
},
"engines": {
"node": ">= 8.0.0"
},
"scripts": {
"start": "node index.js",
"test": "mocha test/*.test.js --check-leaks"
},
"dependencies": {
"body-parser": "^1.19.0",
"express": "^4.16.4"
},
"devDependencies": {
"mocha": "^6.1.4",
"sinon": "^7.3.2",
"supertest": "^4.0.2",
"uuid": "^3.3.2"
}
}
92 changes: 92 additions & 0 deletions run/pubsub/test/app.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// 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:
// This app can only be fully tested when deployed, because
// Pub/Sub requires a live endpoint URL to hit. Nevertheless,
// these tests mock it and partially test it locally.

'use strict';

const assert = require('assert');
const path = require('path');
const supertest = require('supertest');
const sinon = require('sinon');
const uuid = require('uuid');

let request;

describe('Unit Tests', () => {
before(() => {
const app = require(path.join(__dirname, '..', 'app'));
request = supertest(app);
});

describe('should fail', () => {
it(`on a Bad Request with an empty payload`, async () => {
await request
.post('/')
.type('json')
.send('')
.expect(400);
});

it(`on a Bad Request with an invalid payload`, async () => {
await request
.post('/')
.type('json')
.send({nomessage: 'invalid'})
.expect(400);
});

it(`on a Bad Request with an invalid mimetype`, async () => {
await request
.post('/')
.type('text')
.send('{message: true}')
.expect(400);
});
});

describe('should succeed', () => {
beforeEach(() => {
sinon.spy(console, 'error');
sinon.spy(console, 'log');
});
afterEach(() => {
console.error.restore();
console.log.restore();
});

it(`with a minimally valid Pub/Sub Message`, async () => {
await request
.post('/')
.type('json')
.send({message: true})
.expect(204)
.expect(() => assert.ok(console.log.calledWith('Hello World!')));
});

it(`with a populated Pub/Sub Message`, async () => {
const name = uuid.v4();
const data = Buffer.from(name).toString(`base64`);

await request
.post('/')
.type('json')
.send({message: {data}})
.expect(204)
.expect(() => assert.ok(console.log.calledWith(`Hello ${name}!`)));
});
});
});