diff --git a/run/README.md b/run/README.md index 4e24c4c9e4..222a95fe70 100644 --- a/run/README.md +++ b/run/README.md @@ -8,10 +8,11 @@ | Sample | Description | Deploy | | --------------------------------------- | ------------------------ | ------------- | -|[Hello World][helloworld] ➥ | Quickstart | [Run on Google Cloud][run_button_helloworld] | -| [Image Processing][image_processing] | Event-driven image analysis & transformation | [Run on Google Cloud][run_button_image_processing] | -|[Manual Logging][manual_logging] | Structured logging without client library | [Run on Google Cloud][run_button_manual_logging] | -|[Pub/Sub][pubsub] | Event-driven service with a Pub/Sub push subscription | [Run on Google Cloud][run_button_pubsub] | +|[Hello World][helloworld] ➥ | Quickstart | [Run on Google Cloud][run_button_helloworld] | +|[Hello Broken][hello_broken] | Something is wrong, how do you fix it? | [Run on Google Cloud][run_button_hello_broken] | +|[Pub/Sub][pubsub] | Event-driven service with a Pub/Sub push subscription | [Run on Google Cloud][run_button_pubsub] | +|[Image Processing][image_processing] | Event-driven image analysis & transformation | [Run on Google Cloud][run_button_image_processing] | +|[Manual Logging][manual_logging] | Structured logging without client library | [Run on Google Cloud][run_button_manual_logging] | 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). @@ -99,7 +100,7 @@ For more Cloud Run samples beyond Node.js, see the main list in the [Cloud Run S gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/${SAMPLE} gcloud beta run deploy $SAMPLE \ # Needed for Manual Logging sample. - --set-env-var GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT} + --set-env-var GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT} \ --image gcr.io/${GOOGLE_CLOUD_PROJECT}/${SAMPLE} ``` @@ -110,10 +111,12 @@ for more information. [run_build]: https://cloud.google.com/run/docs/building/containers [run_deploy]: https://cloud.google.com/run/docs/deploying [helloworld]: https://github.com/knative/docs/tree/master/docs/serving/samples/hello-world/helloworld-nodejs +[hello_broken]: hello-broken/ +[pubsub]: pubsub/ [image_processing]: image-processing/ [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_hello_broken]: 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/hello-broken +[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 [run_button_image_processing]: 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/image-processing [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 \ No newline at end of file diff --git a/run/hello-broken/Dockerfile b/run/hello-broken/Dockerfile new file mode 100644 index 0000000000..4dc6de2b97 --- /dev/null +++ b/run/hello-broken/Dockerfile @@ -0,0 +1,40 @@ +# 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. + +# [START run_broken_dockerfile] + +# Use the official lightweight Node.js 10 image. +# https://hub.docker.com/_/node +FROM node:10-slim + +# 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 copying both package.json AND package-lock.json (when available). +# Copying this first prevents re-running npm install on every code change. +COPY package*.json ./ + +# Install production dependencies. +# If you add a package-lock.json, speed your build by switching to 'npm ci'. +# RUN npm ci --only=production +RUN npm install --only=production + +# Copy local code to the container image. +COPY . ./ + +# Run the web service on container startup. +CMD [ "npm", "start" ] + +# [END run_broken_dockerfile] diff --git a/run/hello-broken/README.md b/run/hello-broken/README.md new file mode 100644 index 0000000000..8c1e10314b --- /dev/null +++ b/run/hello-broken/README.md @@ -0,0 +1,68 @@ +# Cloud Run Broken Sample + +This sample presents broken code in need of troubleshooting. An alternate +resource at `/improved` shows a more stable implementation with more informative +errors and default values. + +Use it with the [Local Container Troubleshooting tutorial](http://cloud.google.com/run/docs/tutorials/local-troubleshooting). + +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). + +## Local Development + +### `npm run e2e-test` + +``` +export SERVICE_NAME=broken +export CONTAINER_IMAGE=gcr.io/${GOOGLE_CLOUD_PROJECT}/broken +npm run e2e-test +``` + +## Using Testing Scripts + +### url.sh + +The `url.sh` script derives the automatically provisioned URL of a deployed +Cloud Run service. + +```sh +export SERVICE_NAME=broken +export REGION=us-central1 +test/url.sh +``` + +### deploy.sh + +The `deploy.sh` script deploys a Cloud Run service. + +```sh +export SERVICE_NAME=broken +export CONTAINER_IMAGE=gcr.io/${GOOGLE_CLOUD_PROJECT}/broken +export REGION=us-central1 +test/deploy.sh +``` + +### runner.sh + +The `runner.sh` script: + +* Deploys the service to Cloud Run based on the `deploy.sh` script. +* Sets the `BASE_URL` and `ID_TOKEN` environment variables. +* Runs any arguments passed to the `runner.sh` script. +* Tears down the Cloud Run service on completion. + +```sh +test/runner.sh sleep 20 +``` + +## Environment Variables (Testing) + +* `BASE_URL`: Specifies the Cloud Run service URL for end-to-end tests. +* `ID_TOKEN`: JWT token used to authenticate with Cloud Run's IAM-based authentication. +* `REGION`: [`us-central1`] Optional override region for the location of the Cloud Run service. +* `SERVICE_NAME`: The name of the deployed service, used in some API calls and test assertions. + +## Dependencies + +* **express**: Web server framework. +* **got**: [Testing] Used to make HTTP requests of the running service in end-to-end testing. diff --git a/run/hello-broken/index.js b/run/hello-broken/index.js new file mode 100644 index 0000000000..a0a89b1d7a --- /dev/null +++ b/run/hello-broken/index.js @@ -0,0 +1,57 @@ +// 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. + +// [START run_broken_service] +const express = require('express'); +const app = express(); + +app.get('/', (req, res) => { + console.log('hello: received request.'); + + // [START run_broken_service_problem] + const {NAME} = process.env; + if (!NAME) { + // Plain error logs do not appear in Stackdriver Error Reporting. + console.error('Environment validation failed.'); + console.error(new Error('Missing required server parameter')); + return res.status(500).send('Internal Server Error'); + } + // [END run_broken_service_problem] + res.send(`Hello ${NAME}!`); +}); +// [END run_broken_service] + +app.get('/improved', (req, res) => { + console.log('hello: received request.'); + + // [START run_broken_service_upgrade] + const NAME = process.env.NAME || 'World'; + if (!process.env.NAME) { + console.log( + JSON.stringify({ + severity: 'WARNING', + message: `NAME not set, default to '${NAME}'`, + }) + ); + } + // [END run_broken_service_upgrade] + res.send(`Hello ${NAME}!`); +}); + +// [START run_broken_service] +const port = process.env.PORT || 8080; +app.listen(port, () => { + console.log(`hello: listening on port ${port}`); +}); +// [END run_broken_service] diff --git a/run/hello-broken/package.json b/run/hello-broken/package.json new file mode 100644 index 0000000000..e4c08eb4a8 --- /dev/null +++ b/run/hello-broken/package.json @@ -0,0 +1,22 @@ +{ + "name": "hello-broken", + "description": "Broken Cloud Run service for troubleshooting practice", + "version": "1.0.0", + "private": true, + "main": "index.js", + "scripts": { + "start": "node index.js", + "test": "echo \"Error: no test specified\" && exit 0", + "e2e-test": "TARGET=Cloud test/runner.sh mocha test/system.test.js --timeout=20000", + "lint": "eslint '**/*.js'", + "fix": "eslint --fix '**/*.js'" + }, + "author": "Google LLC", + "license": "Apache-2.0", + "dependencies": { + "express": "^4.17.1" + }, + "devDependencies": { + "got": "^9.6.0" + } +} diff --git a/run/hello-broken/test/deploy.sh b/run/hello-broken/test/deploy.sh new file mode 100755 index 0000000000..bfa5c163ec --- /dev/null +++ b/run/hello-broken/test/deploy.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# 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. + +set -eo pipefail; + +requireEnv() { + test "${!1}" || (echo "Environment Variable '$1' not found" && exit 1) +} + +requireEnv SERVICE_NAME +requireEnv CONTAINER_IMAGE + +# Deploy the service +set -x +gcloud beta run deploy "${SERVICE_NAME}" \ + --image="${CONTAINER_IMAGE}" \ + --region="${REGION:-us-central1}" \ + ${FLAGS} \ + --platform=managed \ + --quiet + +echo 'Cloud Run Links:' +echo "- Logs: https://console.cloud.google.com/logs/viewer?project=${GOOGLE_CLOUD_PROJECT}&resource=cloud_run_revision%2Fservice_name%2F${SERVICE_NAME}" +echo "- Console: https://console.cloud.google.com/run/detail/${REGION:-us-central1}/${SERVICE_NAME}/metrics?project=${GOOGLE_CLOUD_PROJECT}" diff --git a/run/hello-broken/test/runner.sh b/run/hello-broken/test/runner.sh new file mode 100755 index 0000000000..f814994f7b --- /dev/null +++ b/run/hello-broken/test/runner.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# 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. + +set -eo pipefail; + +requireEnv() { + test "${!1}" || (echo "Environment Variable '$1' not found" && exit 1) +} +requireEnv SERVICE_NAME + +# The hello-broken sample needs to be tested with the NAME environment variable +# both set and unset. +SERVICE_OVERRIDE="${SERVICE_NAME}-override" + +echo '---' +test/deploy.sh +FLAGS="--set-env-vars NAME=$NAME" SERVICE_NAME=${SERVICE_OVERRIDE} test/deploy.sh + +echo +echo '---' +echo + +# Register post-test cleanup. +# Only needed if deploy completed. +function cleanup { + set -x + gcloud beta run services delete ${SERVICE_NAME} \ + --platform=managed \ + --region="${REGION:-us-central1}" \ + --quiet + gcloud beta run services delete ${SERVICE_OVERRIDE} \ + --platform=managed \ + --region="${REGION:-us-central1}" \ + --quiet +} +trap cleanup EXIT + +# TODO: Perform authentication inside the test. +export ID_TOKEN=$(gcloud auth print-identity-token) +export BASE_URL=$(test/url.sh) +export BASE_URL_OVERRIDE=$(SERVICE_NAME=${SERVICE_OVERRIDE} test/url.sh) +# Do not use exec to preserve trap behavior. +"$@" diff --git a/run/hello-broken/test/system.test.js b/run/hello-broken/test/system.test.js new file mode 100644 index 0000000000..8ecf4f1f4b --- /dev/null +++ b/run/hello-broken/test/system.test.js @@ -0,0 +1,107 @@ +// 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. + +const assert = require('assert'); +const request = require('got'); + +const get = (route, base_url) => { + const {ID_TOKEN} = process.env; + if (!ID_TOKEN) { + throw Error('"ID_TOKEN" environment variable is required.'); + } + + return request(route, { + baseUrl: base_url.trim(), + headers: { + Authorization: `Bearer ${ID_TOKEN.trim()}`, + }, + throwHttpErrors: false, + }); +}; + +describe('End-to-End Tests', () => { + const {NAME} = process.env; + if (!NAME) { + throw Error('"NAME" environment variable is required. For example: Cosmos'); + } + + describe('Default Service', () => { + const {BASE_URL} = process.env; + if (!BASE_URL) { + throw Error( + '"BASE_URL" environment variable is required. For example: https://service-x8xabcdefg-uc.a.run.app' + ); + } + + it('Broken resource fails on any request', async () => { + const response = await get('/', BASE_URL); + assert.strictEqual( + response.statusCode, + 500, + 'Internal service error not found' + ); + }); + + it('Fixed resource successfully falls back to a default value', async () => { + const response = await get('/improved', BASE_URL); + assert.strictEqual( + response.statusCode, + 200, + 'Did not fallback to default as expected' + ); + assert.strictEqual( + response.body, + `Hello ${NAME}!`, + `Expected fallback "World" not found` + ); + }); + }); + + describe('Overridden Service', () => { + const {BASE_URL_OVERRIDE} = process.env; + if (!BASE_URL_OVERRIDE) { + throw Error( + '"BASE_URL_OVERRIDE" environment variable is required. For example: https://service-x8xabcdefg-uc.a.run.app' + ); + } + + it('Broken resource uses the NAME override', async () => { + const response = await get('/', BASE_URL_OVERRIDE); + assert.strictEqual( + response.statusCode, + 200, + 'Did not use the NAME override' + ); + assert.strictEqual( + response.body, + `Hello ${NAME}!`, + `Expected override "${NAME}" not found` + ); + }); + + it('Fixed resource uses the NAME override', async () => { + const response = await get('/improved', BASE_URL_OVERRIDE); + assert.strictEqual( + response.statusCode, + 200, + 'Did not fallback to default as expected' + ); + assert.strictEqual( + response.body, + `Hello ${NAME}!`, + `Expected override "${NAME}" not found` + ); + }); + }); +}); diff --git a/run/hello-broken/test/url.sh b/run/hello-broken/test/url.sh new file mode 100755 index 0000000000..818c818eac --- /dev/null +++ b/run/hello-broken/test/url.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# 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. + +set -eo pipefail; + +requireEnv() { + test "${!1}" || (echo "Environment Variable '$1' not found" && exit 1) +} + +requireEnv SERVICE_NAME + +set -x +gcloud beta run services \ + describe "${SERVICE_NAME}" \ + --region="${REGION:-us-central1}" \ + --format='value(status.domain)' \ + --platform=managed diff --git a/run/logging-manual/README.md b/run/logging-manual/README.md index d6fd78a199..6d1692fdf5 100644 --- a/run/logging-manual/README.md +++ b/run/logging-manual/README.md @@ -42,12 +42,12 @@ test/deploy.sh ### runner.sh -The `runner.sh` script is a helper that facilitates: +The `runner.sh` script: -* Deploy the service to Cloud Run based on the `deploy.sh` script. -* Set the `BASE_URL` and `ID_TOKEN` environment variables. -* Run any arguments passed to the `runner.sh` script. -* Tear down the Cloud Run service on completion. +* Deploys the service to Cloud Run based on the `deploy.sh` script. +* Sets the `BASE_URL` and `ID_TOKEN` environment variables. +* Runs any arguments passed to the `runner.sh` script. +* Tears down the Cloud Run service on completion. ```sh test/runner.sh sleep 20 @@ -55,14 +55,18 @@ test/runner.sh sleep 20 ## Environment Variables (Testing) -* `BASE_URL`: Used to designate the URL of the Cloud Run service under system test. The URL is not deterministic, so the options are to record it at deploy time or use the Cloud Run API to retrieve the value. -* `ID_TOKEN`: Used for authenticated HTTP requests to the Cloud Run service. +* `BASE_URL`: The Cloud Run service URL for end-to-end tests. +* `ID_TOKEN`: JWT token used to authenticate with Cloud Run's IAM-based authentication. * `REGION`: [`us-central1`] Optional override region for the location of the Cloud Run service. -* `SERVICE_NAME`: Used in testing to specify the name of the service. The name may be included in API calls and test conditions. -* `GOOGLE_CLOUD_PROJECT`: Used in production as an override to determination of the GCP Project from the GCP metadata server. Used in testing to configure the Logging client library. +* `SERVICE_NAME`: The name of the deployed service, used in some API calls and test assertions. +* `GOOGLE_CLOUD_PROJECT`: If used, overrides GCP metadata server to determine + the current GCP project. Required by the logging client library. ## Dependencies * **express**: Web server framework. -* **got**: Used to pull the project ID of the running service from the [compute metadata server](https://cloud.google.com/compute/docs/storing-retrieving-metadata) and make system test HTTP requests. This is required in production for log correlation without manually setting the $GOOGLE_CLOUD_PROJECT environment variable. +* **got**: Used to pull the project ID of the running service from the + [compute metadata server](https://cloud.google.com/compute/docs/storing-retrieving-metadata) + and make system test HTTP requests. This is required in production for log correlation without + manually setting the $GOOGLE_CLOUD_PROJECT environment variable. diff --git a/run/logging-manual/test/deploy.sh b/run/logging-manual/test/deploy.sh index f4169587ab..9346d0e707 100755 --- a/run/logging-manual/test/deploy.sh +++ b/run/logging-manual/test/deploy.sh @@ -25,10 +25,11 @@ requireEnv CONTAINER_IMAGE # Deploy the service set -x -gcloud beta --quiet run deploy "${SERVICE_NAME}" \ +gcloud beta run deploy "${SERVICE_NAME}" \ --image="${CONTAINER_IMAGE}" \ --region="${REGION:-us-central1}" \ - --platform=managed + --platform=managed \ + --quiet echo 'Cloud Run Links:' echo "- Logs: https://console.cloud.google.com/logs/viewer?project=${GOOGLE_CLOUD_PROJECT}&resource=cloud_run_revision%2Fservice_name%2F${SERVICE_NAME}" diff --git a/run/logging-manual/test/runner.sh b/run/logging-manual/test/runner.sh index 8d7ebdf321..76c6e29b1f 100755 --- a/run/logging-manual/test/runner.sh +++ b/run/logging-manual/test/runner.sh @@ -32,9 +32,10 @@ echo # Only needed if deploy completed. function cleanup { set -x - gcloud --quiet beta run services delete ${SERVICE_NAME} \ + gcloud beta run services delete ${SERVICE_NAME} \ --platform=managed \ - --region="${REGION:-us-central1}" + --region="${REGION:-us-central1}" \ + --quiet } trap cleanup EXIT