diff --git a/.kokoro/release/common.cfg b/.kokoro/release/common.cfg new file mode 100644 index 000000000..8b85d13a9 --- /dev/null +++ b/.kokoro/release/common.cfg @@ -0,0 +1,16 @@ +# 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" + +# All builds use the trampoline script to run in docker. +build_file: "nodejs-getting-started/.kokoro/trampoline.sh" + +# Download secrets from Cloud Storage. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/nodejs-getting-started" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/node:10-user" +} diff --git a/.kokoro/release/lint.cfg b/.kokoro/release/lint.cfg new file mode 100644 index 000000000..2e1fdbbaa --- /dev/null +++ b/.kokoro/release/lint.cfg @@ -0,0 +1,7 @@ +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/nodejs-getting-started" + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/nodejs-getting-started/.kokoro/presubmit/lint.sh" +} diff --git a/.kokoro/release/lint.sh b/.kokoro/release/lint.sh new file mode 100644 index 000000000..a638f82b0 --- /dev/null +++ b/.kokoro/release/lint.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Copyright 2018-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. + +set -eo pipefail + +export NPM_CONFIG_PREFIX=/home/node/.npm-global + +cd github/nodejs-getting-started + +npm install + +npm run lint diff --git a/background/function/package.json b/background/function/package.json index 091657b37..d85b0a65d 100644 --- a/background/function/package.json +++ b/background/function/package.json @@ -5,10 +5,12 @@ "main": "translate.js", "author": "Google LLC", "license": "Apache-2.0", + "scripts": { + "test": "mocha --exit test/*.test.js" + }, "dependencies": { "@google-cloud/firestore": "^3.0.0", - "@google-cloud/translate": "^5.0.0" + "@google-cloud/translate": "^5.3.0" }, - "devDependencies": { - } + "devDependencies": {} } diff --git a/background/function/translate.js b/background/function/translate.js index 9f21037ff..4bfb27933 100644 --- a/background/function/translate.js +++ b/background/function/translate.js @@ -17,8 +17,8 @@ // result in Firestore. // [START getting_started_background_translate_init] -const {Firestore} = require('@google-cloud/firestore'); -const {Translate} = require('@google-cloud/translate'); +const Firestore = require('@google-cloud/firestore'); +const {Translate} = require('@google-cloud/translate').v2; const firestore = new Firestore(); const translate = new Translate(); @@ -57,3 +57,31 @@ exports.translate = async pubSubEvent => { }); }; // [END getting_started_background_translate] + +exports[`translate-${process.env.unique_id}`] = async pubSubEvent => { + const {language, original} = JSON.parse( + Buffer.from(pubSubEvent.data, 'base64').toString() + ); + + const [ + translated, + { + data: {translations}, + }, + ] = await translate.translate(original, language); + const originalLanguage = translations[0].detectedSourceLanguage; + console.log( + `Translated ${original} in ${originalLanguage} to ${translated} in ${language}.` + ); + + // Store translation in firestore. + await firestore + .collection('translations') + .doc() + .set({ + language, + original, + translated, + originalLanguage, + }); +}; diff --git a/background/package.json b/background/package.json new file mode 100644 index 000000000..e12c07775 --- /dev/null +++ b/background/package.json @@ -0,0 +1,32 @@ +{ + "name": "nodejs-getting-started", + "description": "End to end sample for running Node.js applications on Google Cloud Platform", + "license": "Apache-2.0", + "author": "Google LLC", + "engines": { + "node": ">=10" + }, + "repository": "https://github.com/GoogleCloudPlatform/nodejs-getting-started", + "main": "app.js", + "private": true, + "scripts": { + "start": "node server/app.js", + "test": "mocha --exit **/*.test.js --timeout=50000" + }, + "dependencies": { + "@google-cloud/firestore": "^3.0.0", + "@google-cloud/pubsub": "1.7.3", + "@google-cloud/storage": "^4.0.0", + "body-parser": "^1.18.3" + }, + "devDependencies": { + "mocha": "^7.0.0", + "supertest": "^4.0.2", + "chai": "^4.2.0", + "child_process": "^1.0.2", + "express": "^4.16.4", + "superagent": "^5.2.2", + "superagent-retry": "^0.6.0", + "uuid": "^8.1.0" + } +} diff --git a/background/server/app.js b/background/server/app.js index 60b91aae2..db8bc3518 100644 --- a/background/server/app.js +++ b/background/server/app.js @@ -83,3 +83,4 @@ function requestTranslation(req, res) { res.sendStatus(200); } // [END getting_started_background_app_request] +module.exports = app; diff --git a/background/server/app.yaml b/background/server/app.yaml index 182070dd0..53648e323 100644 --- a/background/server/app.yaml +++ b/background/server/app.yaml @@ -13,4 +13,6 @@ # limitations under the License. # [START getting_started_background_config] runtime: nodejs10 -# [END getting_started_background_config] \ No newline at end of file +# [END getting_started_background_config] + +service: testservice diff --git a/background/test/app.test.js b/background/test/app.test.js new file mode 100644 index 000000000..a98001f9c --- /dev/null +++ b/background/test/app.test.js @@ -0,0 +1,68 @@ +const superagent = require('superagent'); +require('superagent-retry')(superagent); + +const request = require('supertest'); +const cp = require('child_process'); +const path = require('path'); +const projectId = process.env.PROJECT_ID; +const regionId = process.env.REGION_ID; +const app = `https://testservice-dot-${projectId}.${regionId}.r.appspot.com`; +const {expect} = require('chai'); +const {v4: uuidv4} = require('uuid'); + +//in kokoro, need to set project id, auth, and region ID +//use gcloud and firebase cli +//how to get cleanup to wait +describe('behavior of cloud function', function() { + this.timeout(240000); + const uniqueID = uuidv4().split('-')[0]; + before(() => { + cp.execSync(`gcloud config set project ${projectId}`); + cp.execSync(`npm install`, {cwd: path.join(__dirname, '../', 'function')}); + cp.execSync( + `gcloud functions deploy translate-${uniqueID} --allow-unauthenticated --set-env-vars=unique_id=${uniqueID} --runtime nodejs8 --trigger-topic translate`, + {cwd: path.join(__dirname, '../', 'function')} + ); + cp.execSync(`gcloud app deploy`, { + cwd: path.join(__dirname, '../', 'server'), + }); + }); + + after(() => { + try { + cp.execSync(`gcloud app services delete testservice`); + cp.execSync(`gcloud functions delete translate-${uniqueID}`); + cp.execSync( + `firebase firestore:delete --project ${projectId} -r translations` + ); + } catch (err) { + console.log('Was not able to delete resources.'); + } + }); + + it('should get the correct website', async () => { + return await request(app) + .get('/') + .retry(5) + .expect(200); + }); + + it('should ask for a translation', async () => { + return await request(app) + .post('/request-translation') + .type('form') + .send({lang: 'en', v: 'como estas'}) + .retry(5) + .expect(200); + }); + + it("should now contain 'como estas'", async () => { + return await request(app) + .get('/') + .set('Accept-Encoding', 'application/json') + .retry(5) + .end((err, res) => { + expect(res.text.includes('como estas')); + }); + }); +}); diff --git a/bookshelf/app.yaml b/bookshelf/app.yaml index 7621c424a..646d9ff06 100644 --- a/bookshelf/app.yaml +++ b/bookshelf/app.yaml @@ -15,4 +15,5 @@ runtime: nodejs10 instance_class: F2 + # [END bookshelf_app_yml] diff --git a/gce/app.js b/gce/app.js new file mode 100644 index 000000000..d957e321b --- /dev/null +++ b/gce/app.js @@ -0,0 +1,35 @@ +// Copyright 2017 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 express = require('express'); + +const app = express(); + +app.get('/', (req, res) => { + res + .status(200) + .send('Hello, world!') + .end(); +}); + +// Start the server +const PORT = process.env.PORT || 8080; +app.listen(PORT, () => { + console.log(`App listening on port ${PORT}`); + console.log('Press Ctrl+C to quit.'); +}); + +module.exports = app; diff --git a/gce/package.json b/gce/package.json new file mode 100644 index 000000000..5ef2bd0ec --- /dev/null +++ b/gce/package.json @@ -0,0 +1,28 @@ +{ + "name": "gce-hello-world", + "description": "Simple Hello World Node.js sample for Google Compute Engine.", + "version": "0.0.1", + "private": true, + "license": "Apache-2.0", + "author": "Google Inc.", + "repository": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" + }, + "engines": { + "node": ">=8.0.0" + }, + "scripts": { + "start": "node app.js", + "test": "mocha --exit test/*.test.js" + }, + "dependencies": { + "express": "^4.16.3" + }, + "devDependencies": { + "mocha": "^7.2.0", + "uuid": "^8.1.0", + "chai": "^4.2.0", + "node-fetch": "^2.6.0" + } +} diff --git a/gce/startup-script.sh b/gce/startup-script.sh index 8a47454fe..be4e33e70 100644 --- a/gce/startup-script.sh +++ b/gce/startup-script.sh @@ -15,16 +15,18 @@ # [START startup] set -v + # Talk to the metadata server to get the project id PROJECTID=$(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google") -REPOSITORY="[YOUR-REPOSITORY]" +# [END startup] +echo ${PROJECTID} +# [START startup] +REPOSITORY="new-repo" # Install logging monitor. The monitor will automatically pick up logs sent to # syslog. -# [START logging] curl -s "https://storage.googleapis.com/signals-agents/logging/google-fluentd-install.sh" | bash service google-fluentd restart & -# [END logging] # Install dependencies from apt apt-get update @@ -40,10 +42,10 @@ ln -s /opt/nodejs/bin/npm /usr/bin/npm # git requires $HOME and it's not set during the startup script. export HOME=/root git config --global credential.helper gcloud.sh -git clone https://source.developers.google.com/p/${PROJECTID}/r/${REPOSITORY} /opt/app +git clone https://source.developers.google.com/p/${PROJECTID}/r/${REPOSITORY} /opt/app/new-repo # Install app dependencies -cd /opt/app/gce +cd /opt/app/new-repo npm install # Create a nodeapp user. The application will run as this user. @@ -53,7 +55,7 @@ chown -R nodeapp:nodeapp /opt/app # Configure supervisor to run the node app. cat >/etc/supervisor/conf.d/node-app.conf << EOF [program:nodeapp] -directory=/opt/app/gce +directory=/opt/app/new-repo command=npm start autostart=true autorestart=true @@ -67,4 +69,3 @@ supervisorctl reread supervisorctl update # Application should now be running under supervisor -# [END startup] diff --git a/gce/test/app.test.js b/gce/test/app.test.js new file mode 100644 index 000000000..4cd6cfacc --- /dev/null +++ b/gce/test/app.test.js @@ -0,0 +1,69 @@ +const cp = require('child_process'); +const path = require('path'); +const projectId = process.env.PROJECT_ID; +const fetch = require('node-fetch'); +const {expect} = require('chai'); +const {v4: uuidv4} = require('uuid'); +//in kokoro, need to set project id, auth, and region ID +//use gcloud and firebase cli + +//how to get cleanup to wait + +describe('spin up gce instance', function() { + this.timeout(5000000); + this.retries(70); + + const uniqueID = uuidv4().split('-')[0]; + before(() => { + this.timeout(5000000); + cp.execSync(`gcloud config set project ${projectId}`); + try { + cp.execSync( + `gcloud compute instances create my-app-instance-${uniqueID} \ + --image-family=debian-9 \ + --image-project=debian-cloud \ + --machine-type=g1-small \ + --scopes userinfo-email,cloud-platform \ + --metadata app-location=us-central1-f \ + --metadata-from-file startup-script=gce/startup-script.sh \ + --zone us-central1-f \ + --tags http-server`, + {cwd: path.join(__dirname, '../../')} + ); + } catch (err) { + console.log("wasn't able to create the VM instance"); + } + try { + cp.execSync(`gcloud compute firewall-rules create default-allow-http-8080-${uniqueID} \ + --allow tcp:8080 \ + --source-ranges 0.0.0.0/0 \ + --target-tags http-server \ + --description "Allow port 8080 access to http-server"`); + } catch (err) { + console.log("wasn't able to create the firewall rule"); + } + }); + after(() => { + cp.execSync( + `gcloud compute instances delete my-app-instance-${uniqueID} --zone=us-central1-f --delete-disks=all` + ); + cp.execSync( + `gcloud compute firewall-rules delete default-allow-http-8080-${uniqueID}` + ); + }); + + it('should get the instance', async () => { + const externalIP = cp + .execSync( + `gcloud compute instances describe my-app-instance-${uniqueID} \ + --format='get(networkInterfaces[0].accessConfigs[0].natIP)' --zone=us-central1-f` + ) + .toString('utf8') + .trim(); + console.log(`http://${externalIP}:8080`); + const response = await fetch(`http://${externalIP}:8080/`); + const body = await response.text(); + expect(body).to.include('Hello, world!'); + console.log(body); + }); +});