diff --git a/datastore/functions/README.md b/datastore/functions/README.md deleted file mode 100644 index 1aa2c7087e..0000000000 --- a/datastore/functions/README.md +++ /dev/null @@ -1,86 +0,0 @@ -Google Cloud Platform logo - -# Google Cloud Functions Cloud Datastore sample - -This recipe shows you how to read and write an entity in Cloud Datastore from a -Cloud Function. - -View the [source code][code]. - -[code]: index.js - -## Deploy and Test - -1. Follow the [Cloud Functions quickstart guide][quickstart] to setup Cloud -Functions for your project. - -1. Clone this repository: - - git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git - cd nodejs-docs-samples/functions/datastore - -1. Ensure the Cloud Datastore API is enabled: - - [Click here to enable the Cloud Datastore API](https://console.cloud.google.com/flows/enableapi?apiid=datastore.googleapis.com&redirect=https://github.com/GoogleCloudPlatform/nodejs-docs-samples/tree/main/functions/datastore) - -1. Deploy the "get" function with an HTTP trigger: - - gcloud functions deploy get --runtime [YOUR_RUNTIME] --trigger-http - - * Replace `[YOUR_RUNTIME]` with the name of the runtime you are using. For a complete list, - see the [gcloud reference](https://cloud.google.com/sdk/gcloud/reference/functions/deploy#--runtime). - -1. Deploy the "set" function with an HTTP trigger: - - gcloud functions deploy set --runtime [YOUR_RUNTIME] --trigger-http - -1. Deploy the "del" function with an HTTP trigger: - - gcloud functions deploy del --runtime [YOUR_RUNTIME] --trigger-http - -1. Call the "set" function to create a new entity: - - gcloud functions call set --data '{"kind":"Task","key":"sampletask1","value":{"description":"Buy milk"}}' - - or - - curl -H "Content-Type: application/json" -X POST -d '{"kind":"Task","key":"sampletask1","value":{"description":"Buy milk"}}' "https://[YOUR_REGION]-[YOUR_PROJECT_ID].cloudfunctions.net/set" - - * Replace `[YOUR_REGION]` with the region where your function is deployed. - * Replace `[YOUR_PROJECT_ID]` with your Google Cloud Platform project ID. - -1. Call the "get" function to read the newly created entity: - - gcloud functions call get --data '{"kind":"Task","key":"sampletask1"}' - - or - - curl -H "Content-Type: application/json" -X POST -d '{"kind":"Task","key":"sampletask1"}' "https://[YOUR_REGION]-[YOUR_PROJECT_ID].cloudfunctions.net/get" - - * Replace `[YOUR_REGION]` with the region where your function is deployed. - * Replace `[YOUR_PROJECT_ID]` with your Google Cloud Platform project ID. - -1. Call the "del" function to delete the entity: - - gcloud alpha functions call del --data '{"kind":"Task","key":"sampletask1"}' - - or - - curl -H "Content-Type: application/json" -X POST -d '{"kind":"Task","key":"sampletask1"}' "https://[YOUR_REGION]-[YOUR_PROJECT_ID].cloudfunctions.net/del" - - * Replace `[YOUR_REGION]` with the region where your function is deployed. - * Replace `[YOUR_PROJECT_ID]` with your Google Cloud Platform project ID. - -1. Call the "get" function again to verify it was deleted: - - gcloud functions call get --data '{"kind":"Task","key":"sampletask1"}' - - or - - curl -H "Content-Type: application/json" -X POST -d '{"kind":"Task","key":"sampletask1"}' "https://[YOUR_REGION]-[YOUR_PROJECT_ID].cloudfunctions.net/get" - - * Replace `[YOUR_REGION]` with the region where your function is deployed. - * Replace `[YOUR_PROJECT_ID]` with your Google Cloud Platform project ID. - - -[quickstart]: https://cloud.google.com/functions/quickstart diff --git a/datastore/functions/index.js b/datastore/functions/index.js deleted file mode 100644 index 35f30da559..0000000000 --- a/datastore/functions/index.js +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2016 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 {Datastore} = require('@google-cloud/datastore'); - -// Instantiates a client -const datastore = new Datastore(); - -const makeErrorObj = prop => { - return new Error( - `${prop} not provided. Make sure you have a "${prop.toLowerCase()}" property in your request` - ); -}; - -/** - * Gets a Datastore key from the kind/key pair in the request. - * - * @param {object} requestData Cloud Function request data. - * @param {string} requestData.key Datastore key string. - * @param {string} requestData.kind Datastore kind. - * @returns {object} Datastore key object. - */ -const getKeyFromRequestData = requestData => { - if (!requestData.key) { - return Promise.reject(makeErrorObj('Key')); - } - - if (!requestData.kind) { - return Promise.reject(makeErrorObj('Kind')); - } - - return datastore.key([requestData.kind, requestData.key]); -}; - -/** - * Creates and/or updates a record. - * - * @example - * gcloud functions call set --data '{"kind":"Task","key":"sampletask1","value":{"description": "Buy milk"}}' - * - * @param {object} req Cloud Function request context. - * @param {object} req.body The request body. - * @param {string} req.body.kind The Datastore kind of the data to save, e.g. "Task". - * @param {string} req.body.key Key at which to save the data, e.g. "sampletask1". - * @param {object} req.body.value Value to save to Cloud Datastore, e.g. {"description":"Buy milk"} - * @param {object} res Cloud Function response context. - */ -exports.set = async (req, res) => { - // The value contains a JSON document representing the entity we want to save - if (!req.body.value) { - const err = makeErrorObj('Value'); - console.error(err); - res.status(500).send(err.message); - return; - } - - try { - const key = await getKeyFromRequestData(req.body); - const entity = { - key: key, - data: req.body.value, - }; - - await datastore.save(entity); - res.status(200).send(`Entity ${key.path.join('/')} saved.`); - } catch (err) { - console.error(new Error(err.message)); // Add to Stackdriver Error Reporting - res.status(500).send(err.message); - } -}; - -/** - * Retrieves a record. - * - * @example - * gcloud functions call get --data '{"kind":"Task","key":"sampletask1"}' - * - * @param {object} req Cloud Function request context. - * @param {object} req.body The request body. - * @param {string} req.body.kind The Datastore kind of the data to retrieve, e.g. "Task". - * @param {string} req.body.key Key at which to retrieve the data, e.g. "sampletask1". - * @param {object} res Cloud Function response context. - */ -exports.get = async (req, res) => { - try { - const key = await getKeyFromRequestData(req.body); - const [entity] = await datastore.get(key); - - // The get operation returns an empty dictionary for non-existent entities - // We want to throw an error instead - if (!entity) { - throw new Error(`No entity found for key ${key.path.join('/')}.`); - } - - res.status(200).send(entity); - } catch (err) { - console.error(new Error(err.message)); // Add to Stackdriver Error Reporting - res.status(500).send(err.message); - } -}; - -/** - * Deletes a record. - * - * @example - * gcloud functions call del --data '{"kind":"Task","key":"sampletask1"}' - * - * @param {object} req Cloud Function request context. - * @param {object} req.body The request body. - * @param {string} req.body.kind The Datastore kind of the data to delete, e.g. "Task". - * @param {string} req.body.key Key at which to delete data, e.g. "sampletask1". - * @param {object} res Cloud Function response context. - */ -exports.del = async (req, res) => { - // Deletes the entity - // The delete operation will not fail for a non-existent entity, it just - // doesn't delete anything - try { - const key = await getKeyFromRequestData(req.body); - await datastore.delete(key); - res.status(200).send(`Entity ${key.path.join('/')} deleted.`); - } catch (err) { - console.error(new Error(err.message)); // Add to Stackdriver Error Reporting - res.status(500).send(err.message); - } -}; diff --git a/datastore/functions/package.json b/datastore/functions/package.json deleted file mode 100644 index 38aca02bee..0000000000 --- a/datastore/functions/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "nodejs-docs-samples-functions-datastore", - "private": true, - "license": "Apache-2.0", - "author": "Google Inc.", - "repository": { - "type": "git", - "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" - }, - "engines": { - "node": ">=16.0.0" - }, - "scripts": { - "test": "c8 mocha -p -j 2 test/*.test.js --timeout=5000" - }, - "dependencies": { - "@google-cloud/datastore": "^9.0.0" - }, - "devDependencies": { - "@google-cloud/functions-framework": "^3.0.0", - "c8": "^10.0.0", - "child-process-promise": "^2.2.1", - "mocha": "^10.0.0", - "node-fetch": "^3.0.0", - "proxyquire": "^2.1.0", - "sinon": "^18.0.0", - "uuid": "^10.0.0", - "wait-port": "^1.0.4" - } -} diff --git a/datastore/functions/test/index.test.js b/datastore/functions/test/index.test.js deleted file mode 100644 index b683d8ffbb..0000000000 --- a/datastore/functions/test/index.test.js +++ /dev/null @@ -1,301 +0,0 @@ -// 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 assert = require('assert'); -const execPromise = require('child-process-promise').exec; -const path = require('path'); -const uuid = require('uuid'); -const sinon = require('sinon'); -const fetch = require('node-fetch'); -const waitPort = require('wait-port'); -const {Datastore} = require('@google-cloud/datastore'); - -const datastore = new Datastore(); -const program = require('../'); - -const FF_TIMEOUT = 3000; -const cwd = path.join(__dirname, '..'); -const NAME = 'sampletask1'; -const KIND = `Task-${uuid.v4()}`; -const VALUE = { - description: 'Buy milk', -}; - -const errorMsg = msg => - `${msg} not provided. Make sure you have a "${msg.toLowerCase()}" property in your request`; - -const handleLinuxFailures = async proc => { - try { - return await proc; - } catch (err) { - // Timeouts always cause errors on Linux, so catch them - // Don't return proc, as await-ing it re-throws the error - if (!err.name || err.name !== 'ChildProcessError') { - throw err; - } - } -}; - -describe('functions/datastore', () => { - describe('set', () => { - let ffProc; - const PORT = 8080; - const BASE_URL = `http://localhost:${PORT}`; - - before(async () => { - ffProc = execPromise( - `functions-framework --target=set --signature-type=http --port=${PORT}`, - {timeout: FF_TIMEOUT, shell: true, cwd} - ); - await waitPort({port: PORT}); - }); - - after(async () => { - await handleLinuxFailures(ffProc); - }); - - it('set: Fails without a value', async () => { - const req = { - body: {}, - }; - const res = { - status: sinon.stub().returnsThis(), - send: sinon.stub(), - }; - - await program.set(req, res); - - assert.ok(res.status.calledWith(500)); - assert.ok(res.send.calledWith(errorMsg('Value'))); - }); - - it('set: Fails without a key', async () => { - const req = { - body: { - value: VALUE, - }, - }; - const res = { - status: sinon.stub().returnsThis(), - send: sinon.stub(), - }; - await program.set(req, res); - assert.ok(res.status.calledWith(500)); - assert.ok(res.send.calledWith(errorMsg('Key'))); - }); - - it('set: Fails without a kind', async () => { - const req = { - body: { - key: NAME, - value: VALUE, - }, - }; - const res = { - status: sinon.stub().returnsThis(), - send: sinon.stub(), - }; - - await program.set(req, res); - - assert.ok(res.status.calledWith(500)); - assert.ok(res.send.calledWith(errorMsg('Kind'))); - }); - - it('set: Saves an entity', async () => { - const response = await fetch(`${BASE_URL}/set`, { - method: 'POST', - body: JSON.stringify({ - kind: KIND, - key: NAME, - value: VALUE, - }), - headers: {'Content-Type': 'application/json'}, - }); - assert.strictEqual(response.status, 200); - const body = await response.text(); - assert.ok(body.includes(`Entity ${KIND}/${NAME} saved`)); - }); - }); - - describe('get', () => { - let ffProc; - const PORT = 8081; - const BASE_URL = `http://localhost:${PORT}`; - - before(async () => { - ffProc = execPromise( - `functions-framework --target=get --signature-type=http --port=${PORT}`, - {timeout: FF_TIMEOUT, shell: true, cwd} - ); - await waitPort({port: PORT}); - }); - - after(async () => { - await handleLinuxFailures(ffProc); - }); - - it('get: Fails when entity does not exist', async () => { - const response = await fetch(`${BASE_URL}/get`, { - method: 'POST', - body: JSON.stringify({ - kind: KIND, - key: 'nonexistent', - }), - headers: {'Content-Type': 'application/json'}, - validateStatus: () => true, - }); - - assert.strictEqual(response.status, 500); - const body = await response.text(); - assert.ok( - new RegExp( - /(Missing or insufficient permissions.)|(No entity found for key)/ - ).test(body) - ); - }); - - it('get: Finds an entity', async () => { - const response = await fetch(`${BASE_URL}/get`, { - method: 'POST', - body: JSON.stringify({ - kind: KIND, - key: NAME, - }), - headers: {'Content-Type': 'application/json'}, - }); - assert.strictEqual(response.status, 200); - const body = await response.json(); - assert.deepStrictEqual(body, { - description: 'Buy milk', - }); - }); - - it('get: Fails without a key', async () => { - const req = { - body: {}, - }; - const res = { - status: sinon.stub().returnsThis(), - send: sinon.stub(), - }; - - await program.get(req, res); - - assert.ok(res.status.calledWith(500)); - assert.ok(res.send.calledWith(errorMsg('Key'))); - }); - - it('get: Fails without a kind', async () => { - const req = { - body: { - key: NAME, - }, - }; - const res = { - status: sinon.stub().returnsThis(), - send: sinon.stub(), - }; - - await program.get(req, res); - - assert.ok(res.status.calledWith(500)); - assert.ok(res.send.calledWith(errorMsg('Kind'))); - }); - }); - - describe('del', () => { - let ffProc; - const PORT = 8082; - const BASE_URL = `http://localhost:${PORT}`; - - before(async () => { - ffProc = execPromise( - `functions-framework --target=del --signature-type=http --port=${PORT}`, - {timeout: FF_TIMEOUT, shell: true, cwd} - ); - await waitPort({port: PORT}); - }); - - after(async () => { - await handleLinuxFailures(ffProc); - }); - - it('del: Fails without a key', async () => { - const req = { - body: {}, - }; - const res = { - status: sinon.stub().returnsThis(), - send: sinon.stub(), - }; - - await program.del(req, res); - - assert.ok(res.status.calledWith(500)); - assert.ok(res.send.calledWith(errorMsg('Key'))); - }); - - it('del: Fails without a kind', async () => { - const req = { - body: { - key: NAME, - }, - }; - const res = { - status: sinon.stub().returnsThis(), - send: sinon.stub(), - }; - - await program.del(req, res); - - assert.ok(res.status.calledWith(500)); - assert.ok(res.send.calledWith(errorMsg('Kind'))); - }); - - it("del: Doesn't fail when entity does not exist", async () => { - const response = await fetch(`${BASE_URL}/del`, { - method: 'POST', - body: JSON.stringify({ - kind: KIND, - key: 'nonexistent', - }), - headers: {'Content-Type': 'application/json'}, - }); - assert.strictEqual(response.status, 200); - const body = await response.text(); - assert.strictEqual(body, `Entity ${KIND}/nonexistent deleted.`); - }); - - it('del: Deletes an entity', async () => { - const response = await fetch(`${BASE_URL}/del`, { - method: 'POST', - body: JSON.stringify({ - kind: KIND, - key: NAME, - }), - headers: {'Content-Type': 'application/json'}, - }); - assert.strictEqual(response.status, 200); - const body = await response.text(); - assert.strictEqual(body, `Entity ${KIND}/${NAME} deleted.`); - - const key = datastore.key([KIND, NAME]); - const [entity] = await datastore.get(key); - assert.ok(!entity); - }); - }); -});