From d48c9ec530607b8579458eb63a7785c7b7608df3 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Fri, 16 Nov 2018 10:51:40 -0800 Subject: [PATCH 1/3] Update CI config. --- .circleci/config.yml | 123 ++++++++++++------------- .circleci/key.json.enc | Bin 0 -> 2368 bytes .circleci/npm-install-retry.js | 60 ++++++++++++ package.json | 34 +++---- src/cli/controller.js | 20 ++-- src/client/rest-client.js | 2 +- src/model/functions.js | 20 ++-- src/service/rest-service.js | 19 ++-- test/system/cli/index.test.js | 22 ++--- test/unit/service/rest-service.test.js | 6 +- 10 files changed, 186 insertions(+), 120 deletions(-) create mode 100644 .circleci/key.json.enc create mode 100755 .circleci/npm-install-retry.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 9a9c06a..89f9f64 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,90 +3,89 @@ workflows: version: 2 tests: jobs: &workflow_jobs - - node6: - filters: - tags: - only: /.*/ - # - node8: - # filters: - # tags: - # only: /.*/ - # - node10: - # filters: - # tags: - # only: /.*/ - lint: - requires: - - node6 - # - node8 - # - node10 - filters: + filters: &all_commits tags: only: /.*/ - - system_tests: + - node6: requires: - lint - filters: - branches: - only: master - tags: - only: '/^v[\d.]+$/' - - publish_npm: + filters: *all_commits + - node8: requires: - - system_tests + - lint + filters: *all_commits + nightly: + triggers: + - schedule: + cron: 0 7 * * * filters: branches: - ignore: /.*/ - tags: - only: '/^v[\d.]+$/' + only: master + jobs: *workflow_jobs jobs: + lint: + docker: + - image: 'node:8' + user: node + steps: + - checkout + - run: &npm_install_and_link + name: Install and link the module + command: |- + mkdir -p /home/node/.npm-global + ./.circleci/npm-install-retry.js + environment: + NPM_CONFIG_PREFIX: /home/node/.npm-global + - run: + name: Run linting. + command: npm run lint + environment: + NPM_CONFIG_PREFIX: /home/node/.npm-global node6: docker: - image: 'node:6' - steps: &unit_tests_steps + user: node + steps: &tests_steps - checkout + - run: + name: Decrypt credentials. + command: | + if ! [[ -z "${SERVICE_ACCOUNT_ENCRYPTION_KEY}" ]]; then + for encrypted_key in .circleci/*.json.enc; do + openssl aes-256-cbc -d -in $encrypted_key \ + -out $(echo $encrypted_key | sed 's/\.enc//') \ + -k "${SERVICE_ACCOUNT_ENCRYPTION_KEY}" + done + fi - run: name: Install and configure Cloud SDK command: | - echo $KEYFILE > key.json - curl https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-209.0.0-linux-x86_64.tar.gz | tar xz + curl https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-225.0.0-linux-x86_64.tar.gz | tar xz mv ./google-cloud-sdk /opt export PATH=$PATH:/opt/google-cloud-sdk/bin gcloud components install beta -q gcloud components update -q gcloud config set project cloud-functions-emulator gcloud config set compute/region us-central1 - gcloud auth activate-service-account --key-file key.json - - run: yarn - - run: yarn test + gcloud auth activate-service-account --key-file .circleci/key.json + - run: *npm_install_and_link + - run: + name: Run tests. + command: npm test + environment: + GCLOUD_PROJECT: cloud-functions-emulator + GOOGLE_APPLICATION_CREDENTIALS: .circleci/key.json - run: node_modules/.bin/codecov - # node8: - # docker: - # - image: 'node:8' - # steps: *unit_tests_steps - # node10: - # docker: - # - image: 'node:10' - # steps: *unit_tests_steps - lint: - docker: - - image: 'node:6' - steps: - - checkout - - run: yarn install - - run: npm run lint - system_tests: + - run: + name: Remove unencrypted key. + command: | + if ! [[ -z "${SERVICE_ACCOUNT_ENCRYPTION_KEY}" ]]; then + rm .circleci/*.json + fi + when: always + node8: docker: - - image: 'node:6' + - image: 'node:8' user: node - steps: - - checkout - - run: yarn - - run: yarn run system-test - publish_npm: - docker: - - image: 'node:10' - steps: - - checkout - - run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc - - run: npm publish + steps: *tests_steps diff --git a/.circleci/key.json.enc b/.circleci/key.json.enc new file mode 100644 index 0000000000000000000000000000000000000000..ac007af5a8820115994bb9bfdc38556f8fc67b86 GIT binary patch literal 2368 zcmV-G3BUGJVQh3|WM5ywn3IQKGjdF^jB9To&zOtnn!d|Mw!V93cwT?kVs3gs#|6%c zHHNSD17-6>W7K(m#H5RbPz0vM>ChfJ3L%fGg5nIu%}89*n_g?R7O~#+aA^Yv#+D$Q zkJ&4704-&^Mr0@`!C9Xg!bvr0U9i?Dq7+}?3UYHBF{GEdGK;a`t}^uhsQZ#SickyT z6=92!7EtDpKHp!T_?}kYn&@GqI~Mq?83iNng7ut>xpizW(%h&^E=g=hHx4 z`y}mUpd+8qIXF@cAy3>Ec5(#oTh*k6S)l_?3_)&?DgP<_jfeDYYBh8|i=-@zEN3Qw z?vC!_3@}@`u4;a@1C0`s9%*&8=1U{ESvFIODI4U>2(O^LCfg1oAdJsR&G6&i!{%O( zg=6=%(AhpO^*4+Se>XC; z$XN0`k_Hk>g;E^QAXJEiudPjKIqx^wp8x|PL^JVy3Q;BASY>^PvwD;HX=HIT{?W){ z;KE95P*Be@NZT*c0?7d9(%N2vnY2_ARB>W%N}9x#Y)8?LN(`d^djydGAteoTUD$M# zl*Y<8P9MN}mwQS!mcWZq#Jr==AIB}9k@SQynn5}nD;qdR%9V^{-7*@KImi*nc?B2^ z3+)D0l+Mp7BM3>o|v(xH`z*ZL^`O*-Ibs<>H1`2#B3qqSI6 z8(9m3tGbne$#b9mBTOD_QWW*vm)qj=p@IbwSYF^r^_FJ}b)CZ|@2qpYDp|jHB^vQU z_Xw}hL%abg_hiBomK3CV<0J?!EAq~_r!M}6Ec3Vthr+}3GQ$W)JPVjuCn*x% zyx_VOcTyFli${h4Np4|J!}#LNL-iBGw)Et;6e>>DxHS*EKh!V|THj`%I`1H{l%OT& zO+ge%ZSRCU&Dg<#mLqyS;<_^5Rhhh!slv|^)8W<-e$eo`MRdPa=7zb|Nc5B1@a(4& zS{m_Bil({z#OdYzw!A>d9``pgy%-d#< z?cN0McvI8=sl%+~%Yk*h&m4Triw;Oxus&TX%#zX95jT|TLHyD_YK75~iI7!qioi!Q*5R5n+P<4O|p2jf>^A>rb1pL5}`Bh5~NsMXuP7 z;-&y9C9^If0N>d^&fiEnYAF31W_7%u_~lwtV4Y3H^@fHOe`H4~Kc%f7IR(_!8!fJ+ zeNouSBtyUtse}=i5{@>PaB6}j`ZsRuzzA#5f#gKYcvu7-hA>ce5V}m>qI=OUa3OdU zzmOgxvTS1{-A?t$-saflxX`X- z(mjh?lfKn2Nw*yQI9g!hY3)m*X%Mjj#BofocvlSsEaf$OOT{ z?7T&2Qb(EkmnN`pC})Ta_3mLAN3#1*m&`&B_oN|n7eoLCiBw56HCtlelk4n zkb8i?vp-d`4o2l83y&XDU`3TByu3ASl3;Cia#-64PF%5iKM3 z^Wj9SW+aK^En`#Elf#C@pJ6-RMS#<(n|m9{2egSKhZ`?prVjJyj6qsZVlGJUEANXu zB!a)qo9T>U7Qj|K5&+#1&}{IUUonAEE1?KXNcQksoE`*G)|P*M(bKj%pCLYp!z72mF3{jh278cRim((I&X~#U6Xq|)X(Q50 z(56>bb=*F-S~g&KOjaUpO)To-dTh=7IO3n@Im}&M$78&!p^RYw!P5gx8L>97f mO8HTbgh5ziwyynL_4_Gpp>m6395o&FRJq^*g91~wNu4Rx@{nQx literal 0 HcmV?d00001 diff --git a/.circleci/npm-install-retry.js b/.circleci/npm-install-retry.js new file mode 100755 index 0000000..ae3220d --- /dev/null +++ b/.circleci/npm-install-retry.js @@ -0,0 +1,60 @@ +#!/usr/bin/env node + +let spawn = require('child_process').spawn; + +// +//USE: ./index.js [... NPM ARGS] +// + +let timeout = process.argv[2] || 60000; +let attempts = process.argv[3] || 3; +let args = process.argv.slice(4); +if (args.length === 0) { + args = ['install']; +} + +(function npm() { + let timer; + args.push('--verbose'); + let proc = spawn('npm', args); + proc.stdout.pipe(process.stdout); + proc.stderr.pipe(process.stderr); + proc.stdin.end(); + proc.stdout.on('data', () => { + setTimer(); + }); + proc.stderr.on('data', () => { + setTimer(); + }); + + // side effect: this also restarts when npm exits with a bad code even if it + // didnt timeout + proc.on('close', (code, signal) => { + clearTimeout(timer); + if (code || signal) { + console.log('[npm-are-you-sleeping] npm exited with code ' + code + ''); + + if (--attempts) { + console.log('[npm-are-you-sleeping] restarting'); + npm(); + } else { + console.log('[npm-are-you-sleeping] i tried lots of times. giving up.'); + throw new Error("npm install fails"); + } + } + }); + + function setTimer() { + clearTimeout(timer); + timer = setTimeout(() => { + console.log('[npm-are-you-sleeping] killing npm with SIGTERM'); + proc.kill('SIGTERM'); + // wait a couple seconds + timer = setTimeout(() => { + // its it's still not closed sigkill + console.log('[npm-are-you-sleeping] killing npm with SIGKILL'); + proc.kill('SIGKILL'); + }, 2000); + }, timeout); + } +})(); diff --git a/package.json b/package.json index 7cef08b..9322b13 100644 --- a/package.json +++ b/package.json @@ -73,17 +73,17 @@ "generate-scaffolding": "repo-tools generate contributors coc contributing license pr_template pkgjson" }, "dependencies": { - "@google-cloud/storage": "^1.7.0", - "adm-zip": "^0.4.11", - "ajv": "^6.5.2", + "@google-cloud/storage": "^2.3.1", + "adm-zip": "^0.4.13", + "ajv": "^6.5.5", + "axios": "^0.18.0", "body-parser": "^1.18.3", "chokidar": "^2.0.4", - "cli-table2": "0.2.0", - "colors": "^1.3.1", + "cli-table3": "^0.5.1", + "colors": "^1.3.2", "configstore": "^4.0.0", - "express": "^4.16.3", + "express": "^4.16.4", "googleapis": "^35.0.0", - "got": "^9.0.0", "http-proxy": "^1.17.0", "lodash": "4.17.11", "make-dir": "1.3.0", @@ -92,23 +92,23 @@ "semver": "5.6.0", "serializerr": "1.0.3", "tmp": "0.0.33", - "uuid": "3.2.1", + "uuid": "3.3.2", "winston": "3.1.0", "xdg-basedir": "3.0.0", - "yargs": "11.0.0" + "yargs": "12.0.2" }, "devDependencies": { "@google-cloud/nodejs-repo-tools": "^3.0.0", - "codecov": "^3.0.4", + "codecov": "^3.1.0", "intelli-espower-loader": "^1.0.1", "mocha": "^5.2.0", - "nock": "^10.0.0", - "nyc": "^13.0.0", - "power-assert": "^1.6.0", - "proxyquire": "^2.0.0", - "semistandard": "^13.0.0", + "nock": "^10.0.2", + "nyc": "^13.1.0", + "power-assert": "^1.6.1", + "proxyquire": "^2.1.0", + "semistandard": "^13.0.1", "semistandard-format": "^3.0.0", - "sinon": "^7.0.0", - "supertest": "^3.0.0" + "sinon": "^7.1.1", + "supertest": "^3.3.0" } } diff --git a/src/cli/controller.js b/src/cli/controller.js index d9b3ee6..c13dc1d 100644 --- a/src/cli/controller.js +++ b/src/cli/controller.js @@ -34,9 +34,9 @@ require('colors'); const _ = require('lodash'); const AdmZip = require('adm-zip'); +const axios = require('axios'); const exec = require('child_process').exec; const fs = require('fs'); -const got = require('got'); const path = require('path'); const spawn = require('child_process').spawn; const Storage = require('@google-cloud/storage'); @@ -367,14 +367,15 @@ class Controller { this.log(`You paused execution. Connect to the debugger on port ${opts.port} to resume execution and begin debugging.`); } - return got.post(`http://${this.config.host}:${this.config.supervisorPort}/api/debug`, { - body: { + return axios.request({ + url: `http://${this.config.host}:${this.config.supervisorPort}/api/debug`, + method: 'POST', + data: { type: type, name: cloudfunction.name, port: opts.port, pause: opts.pause - }, - json: true + } }); }); } @@ -563,12 +564,13 @@ class Controller { reset (name, opts) { return this.client.getFunction(name) .then(([cloudfunction]) => { - return got.post(`http://${this.config.host}:${this.config.supervisorPort}/api/reset`, { - body: { + return axios.request({ + url: `http://${this.config.host}:${this.config.supervisorPort}/api/reset`, + method: 'POST', + data: { name: cloudfunction.name, keep: opts.keep - }, - json: true + } }); }); } diff --git a/src/client/rest-client.js b/src/client/rest-client.js index ffda5c9..57e08e8 100644 --- a/src/client/rest-client.js +++ b/src/client/rest-client.js @@ -16,7 +16,7 @@ 'use strict'; const _ = require('lodash'); -const google = require('googleapis'); +const { google } = require('googleapis'); const net = require('net'); const path = require('path'); const url = require('url'); diff --git a/src/model/functions.js b/src/model/functions.js index 39147b6..70a547b 100644 --- a/src/model/functions.js +++ b/src/model/functions.js @@ -17,9 +17,9 @@ const _ = require('lodash'); const AdmZip = require('adm-zip'); +const axios = require('axios'); const Configstore = require('configstore'); const fs = require('fs'); -const got = require('got'); const logger = require('winston'); const os = require('os'); const path = require('path'); @@ -388,11 +388,12 @@ class Functions { return new Promise((resolve, reject) => { setTimeout(() => { - got.post(`${this.getSupervisorHost()}/api/deploy`, { - body: { + return axios.request({ + url: `${this.getSupervisorHost()}/api/deploy`, + method: 'POST', + data: { name: cloudfunction.name - }, - json: true + } }).then(resolve, (err) => { if (err && err.response && err.response.body) { if (err.response.body.error) { @@ -521,11 +522,12 @@ class Functions { // Delete the CloudFunction this.adapter.deleteFunction(name) .then(() => { - return got.post(`${this.getSupervisorHost()}/api/delete`, { - body: { + return axios.request({ + url: `${this.getSupervisorHost()}/api/delete`, + method: 'POST', + data: { name: cloudfunction.name - }, - json: true + } }); }) .then(() => { diff --git a/src/service/rest-service.js b/src/service/rest-service.js index 3c5478f..4c78904 100644 --- a/src/service/rest-service.js +++ b/src/service/rest-service.js @@ -15,11 +15,11 @@ 'use strict'; +const axios = require('axios'); const bodyParser = require('body-parser'); const Configstore = require('configstore'); const express = require('express'); const fs = require('fs'); -const got = require('got'); const logger = require('winston'); const path = require('path'); const url = require('url'); @@ -147,11 +147,14 @@ class RestService extends Service { event.auth = req.body.auth || { admin: true }; } - return got.post(`${this.functions.getSupervisorHost()}/${req.params.project}/${req.params.location}/${req.params.name}`, { - body: JSON.stringify(cloudfunction.httpsTrigger ? event.data : event), + return axios.request({ + url: `${this.functions.getSupervisorHost()}/${req.params.project}/${req.params.location}/${req.params.name}`, + method: 'POST', + data: JSON.stringify(cloudfunction.httpsTrigger ? event.data : event), headers: { 'Content-Type': 'application/json' - } + }, + responseType: 'text' }); }) .then((response) => { @@ -233,8 +236,11 @@ class RestService extends Service { if (typeof doc === 'object' && Object.keys(doc).length > 0 && doc.version === API_VERSION) { return doc; } - return got(DISCOVERY_URL, { - query: { + + return axios.request({ + url: DISCOVERY_URL, + method: 'GET', + params: { version: req.query.version } }) @@ -346,6 +352,7 @@ class RestService extends Service { * @param {object} res The response. */ listFunctions (req, res) { + console.error('listFunctions'); const location = CloudFunction.formatLocation(req.params.project, req.params.location); logger.debug('RestService#listFunctions', location); return this.functions.listFunctions(location, { diff --git a/test/system/cli/index.test.js b/test/system/cli/index.test.js index b4ac47d..524b924 100644 --- a/test/system/cli/index.test.js +++ b/test/system/cli/index.test.js @@ -15,7 +15,7 @@ 'use strict'; -const got = require(`got`); +const axios = require(`axios`); const path = require(`path`); process.env.XDG_CONFIG_HOME = path.join(__dirname, `../`); @@ -437,9 +437,7 @@ function makeTests (service, override) { }); it(`should call (via request) an HTTP function that exceeds its timeout`, () => { - return got(`http://localhost:${SUPERVISOR_PORT}/${PROJECT_ID}/${REGION}/helloSlow`, { - json: true - }).then((response) => { + return axios.get(`http://localhost:${SUPERVISOR_PORT}/${PROJECT_ID}/${REGION}/helloSlow`).then((response) => { assert.fail(`should have failed`); }).catch((err) => { assert.equal(err.response.statusCode, 500); @@ -459,9 +457,7 @@ function makeTests (service, override) { }); it(`should call (via request) an HTTP function that fails to respond (crashes asynchronously)`, () => { - return got(`http://localhost:${SUPERVISOR_PORT}/${PROJECT_ID}/${REGION}/helloNoResponse`, { - json: true - }).then((response) => { + return axios.get(`http://localhost:${SUPERVISOR_PORT}/${PROJECT_ID}/${REGION}/helloNoResponse`).then((response) => { assert.fail(`should have failed`); }).catch((err) => { assert.equal(err.response.statusCode, 500); @@ -492,12 +488,12 @@ function makeTests (service, override) { }); it(`should call an HTTP function via trigger URL`, () => { - return got(`http://localhost:${SUPERVISOR_PORT}/${PROJECT_ID}/${REGION}/helloGET`, { + return axios.request({ + url: `http://localhost:${SUPERVISOR_PORT}/${PROJECT_ID}/${REGION}/helloGET`, method: 'GET', headers: { 'x-api-key': 'any' - }, - json: true + } }).then((response) => { assert.equal(response.body.headers[`x-api-key`], `any`); assert.equal(response.body.method, `GET`); @@ -507,12 +503,12 @@ function makeTests (service, override) { }); it(`should call an HTTP function via trigger URL with extras`, () => { - return got(`http://localhost:${SUPERVISOR_PORT}/${PROJECT_ID}/${REGION}/helloGET/test?foo=bar&beep=boop`, { + return axios.request({ + url: `http://localhost:${SUPERVISOR_PORT}/${PROJECT_ID}/${REGION}/helloGET/test?foo=bar&beep=boop`, method: 'GET', headers: { 'x-api-key': 'any' - }, - json: true + } }).then((response) => { assert.equal(response.body.headers[`x-api-key`], `any`); assert.equal(response.body.method, `GET`); diff --git a/test/unit/service/rest-service.test.js b/test/unit/service/rest-service.test.js index 1b659df..1996129 100644 --- a/test/unit/service/rest-service.test.js +++ b/test/unit/service/rest-service.test.js @@ -97,7 +97,7 @@ describe('unit/service/rest-service', () => { nock('https://faked.com') .post('/fake-project/us-central1/test-function', { - auth: {admin: true} + auth: { admin: true } }) .reply(200); @@ -124,7 +124,7 @@ describe('unit/service/rest-service', () => { nock('https://faked.com') .post('/fake-project/us-central1/test-function', { - auth: {admin: false}, + auth: { admin: false }, resource: 'custom.resource' }) .reply(200); @@ -132,7 +132,7 @@ describe('unit/service/rest-service', () => { request(service.server) .post('/v1/projects/fake-project/locations/us-central1/functions/test-function:call') .send({ - auth: {admin: false}, + auth: { admin: false }, resource: 'custom.resource' }) .expect(200, done); From 4dc7050ad804422974294ff555a326366e665831 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Sat, 17 Nov 2018 02:29:42 -0800 Subject: [PATCH 2/3] Fix most test failures. --- CHANGELOG.md | 9 ++++ package.json | 9 ++-- src/cli/commands/delete.js | 2 +- src/cli/commands/deploy.js | 4 +- src/cli/controller.js | 8 ++- src/client/rest-client.js | 93 +++++++---------------------------- src/model/functions.js | 2 +- src/service/rest-service.js | 77 +---------------------------- test/system/cli/index.test.js | 15 +++--- 9 files changed, 47 insertions(+), 172 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b4dfe9..20dcf30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +##### 1.0.0-beta.6 - XX November 2018 + +###### Breaking changes +- Removed support for using the Gcloud SDK to interact with the emulator +- Removed used of googleapis library + +###### Other +- Upgraded all dependencies + ##### 1.0.0-beta.5 - 31 July 2018 ###### Bug fixes diff --git a/package.json b/package.json index 9322b13..1eba2a1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@google-cloud/functions-emulator", "description": "Google Cloud Functions Emulator", - "version": "1.0.0-beta.5", + "version": "1.0.0-beta.6", "license": "Apache-2.0", "author": "Google Inc.", "engines": { @@ -59,7 +59,7 @@ "scripts": { "lint": "semistandard", "fix": "semistandard --fix", - "mocha": "mocha test/_setup test/ --recursive -t 120000 -S -R spec --require intelli-espower-loader", + "mocha": "mocha test/_setup test/system/ --recursive -t 120000 -S -R spec --require intelli-espower-loader", "unit-test": "npm run mocha -- --grep \"unit/\"", "system-test": "npm run mocha -- --grep \"system/\"", "mocha-debug": "mocha debug test/_setup test/ --recursive -t 120000 -S -R spec --require intelli-espower-loader", @@ -73,7 +73,7 @@ "generate-scaffolding": "repo-tools generate contributors coc contributing license pr_template pkgjson" }, "dependencies": { - "@google-cloud/storage": "^2.3.1", + "@google-cloud/storage": "1.7.0", "adm-zip": "^0.4.13", "ajv": "^6.5.5", "axios": "^0.18.0", @@ -83,7 +83,6 @@ "colors": "^1.3.2", "configstore": "^4.0.0", "express": "^4.16.4", - "googleapis": "^35.0.0", "http-proxy": "^1.17.0", "lodash": "4.17.11", "make-dir": "1.3.0", @@ -98,7 +97,7 @@ "yargs": "12.0.2" }, "devDependencies": { - "@google-cloud/nodejs-repo-tools": "^3.0.0", + "@google-cloud/nodejs-repo-tools": "2.3.5", "codecov": "^3.1.0", "intelli-espower-loader": "^1.0.1", "mocha": "^5.2.0", diff --git a/src/cli/commands/delete.js b/src/cli/commands/delete.js index 88cf918..651775a 100644 --- a/src/cli/commands/delete.js +++ b/src/cli/commands/delete.js @@ -59,7 +59,7 @@ exports.handler = (opts) => { function poll () { controller.write('.'); controller.client.getOperation(operation.name) - .then(([operation]) => { + .then((operation) => { if (!operation.done) { setTimeout(poll, 500); } else { diff --git a/src/cli/commands/deploy.js b/src/cli/commands/deploy.js index 72daa0b..55e20b1 100644 --- a/src/cli/commands/deploy.js +++ b/src/cli/commands/deploy.js @@ -144,13 +144,13 @@ exports.handler = (opts) => { return controller.doIfRunning() // Deploy the function .then(() => controller.deploy(opts.functionName, opts)) - .then(([operation, response]) => { + .then(([operation]) => { // Poll the operation return new Promise((resolve, reject) => { function poll () { controller.write('.'); controller.client.getOperation(operation.name) - .then(([operation]) => { + .then((operation) => { if (!operation.done) { setTimeout(poll, 500); } else { diff --git a/src/cli/controller.js b/src/cli/controller.js index c13dc1d..41cdb9b 100644 --- a/src/cli/controller.js +++ b/src/cli/controller.js @@ -23,9 +23,6 @@ * CLI - - Emulator * |--<-- RestClient - HTTP1.1 - JSON --<--| * - * The Gcloud SDK can be used to talk to the Emulator as well, just do: - * - * gcloud config set api_endpoint_overrides/cloudfunctions http://localhost:8008/ */ 'use strict'; @@ -391,10 +388,11 @@ class Controller { .then( () => this.undeploy(name).then(() => this._create(name, opts)), (err) => { - if (err.code === 404 || err.code === 5) { + const error = err.response.data.error; + if (error.code === 404 || error.code === 5) { return this._create(name, opts); } - return Promise.reject(err); + return Promise.reject(error); } ); } diff --git a/src/client/rest-client.js b/src/client/rest-client.js index 57e08e8..7f6872a 100644 --- a/src/client/rest-client.js +++ b/src/client/rest-client.js @@ -16,7 +16,7 @@ 'use strict'; const _ = require('lodash'); -const { google } = require('googleapis'); +const axios = require('axios'); const net = require('net'); const path = require('path'); const url = require('url'); @@ -27,34 +27,14 @@ const Model = require('../model'); const { CloudFunction } = Model; class RestClient extends Client { - _action (method, params) { - return this.getService() - .then((functionsService) => { - return new Promise((resolve, reject) => { - _.get(functionsService, method).call(functionsService, params, (err, body, response) => { - if (err) { - reject(err); - } else { - resolve([body, response]); - } - }); - }); - }); - } - callFunction (name, data, opts) { var resource = { data: data }; if (opts) { resource = _.merge(resource, opts); } - return this._action( - 'projects.locations.functions.call', - { - name: CloudFunction.formatName(this.config.projectId, this.config.region, name), - resource: resource - } - ).then(([body, response]) => { + return axios.post(this.getUrl(`${CloudFunction.formatName(this.config.projectId, this.config.region, name)}:call`), resource).then((response) => { + const body = response.data; if (body.result && typeof body.result === 'string') { try { body.result = JSON.parse(body.result); @@ -73,50 +53,21 @@ class RestClient extends Client { } createFunction (cloudfunction) { - return this._action( - 'projects.locations.functions.create', - { - location: CloudFunction.formatLocation(this.config.projectId, this.config.region), - resource: cloudfunction - } - ); + return axios.post(this.getUrl(`${CloudFunction.formatLocation(this.config.projectId, this.config.region)}/functions`), cloudfunction).then((response) => [response.data, response]); } deleteFunction (name) { - return this._action( - 'projects.locations.functions.delete', - { - name: CloudFunction.formatName(this.config.projectId, this.config.region, name) - } - ); + return axios.request({ + url: this.getUrl(CloudFunction.formatName(this.config.projectId, this.config.region, name)), + method: 'DELETE' + }).then((response) => [response.data, response]); } generateUploadUrl (name) { - return this._action( - 'projects.locations.functions.generateUploadUrl', - { - parent: CloudFunction.formatLocation(this.config.projectId, this.config.region) - } - ); - } - - getService () { - return new Promise((resolve, reject) => { - const discoveryPath = '$discovery/rest'; - const parts = url.parse(this.getUrl(discoveryPath)); - const discoveryUrl = url.format(_.merge(parts, { - pathname: discoveryPath, - search: '?version=v1' - })); - - google.discoverAPI(discoveryUrl, (err, functions) => { - if (err) { - reject(err); - } else { - resolve(functions); - } - }); - }); + return axios.post( + this.getUrl(`${CloudFunction.formatLocation(this.config.projectId, this.config.region)}/functions:generateUploadUrl`) + ) + .then((response) => [response.data, response]); } getUrl (pathname) { @@ -129,26 +80,20 @@ class RestClient extends Client { } getFunction (name) { - return this._action( - 'projects.locations.functions.get', - { - name: CloudFunction.formatName(this.config.projectId, this.config.region, name) - } - ).then(([body, response]) => [new CloudFunction(body.name, body), response]); + return axios.get(this.getUrl(CloudFunction.formatName(this.config.projectId, this.config.region, name))) + .then((response) => [new CloudFunction(response.data.name, response.data), response]); } getOperation (name) { - return this._action('operations.get', { name }); + return axios.get(this.getUrl(name)).then(response => response.data); } listFunctions () { - return this._action( - 'projects.locations.functions.list', - { - pageSize: 100, - parent: CloudFunction.formatLocation(this.config.projectId, this.config.region) + return axios.get(this.getUrl(`${CloudFunction.formatLocation(this.config.projectId, this.config.region)}/functions`), { + params: { + pageSize: 100 } - ).then(([body, response]) => [body.functions.map((cloudfunction) => new CloudFunction(cloudfunction.name, cloudfunction)), response]); + }).then((response) => [response.data.functions.map((cloudfunction) => new CloudFunction(cloudfunction.name, cloudfunction)), response]); } testConnection () { diff --git a/src/model/functions.js b/src/model/functions.js index 70a547b..4d46fe8 100644 --- a/src/model/functions.js +++ b/src/model/functions.js @@ -339,7 +339,7 @@ class Functions { } } - logger.debug(JSON.stringify(request.function, null, 2)); + logger.debug(request.function); // TODO: Filter out fields that cannot be edited by the user cloudfunction = this.cloudfunction(cloudfunction.name, cloudfunction); diff --git a/src/service/rest-service.js b/src/service/rest-service.js index 4c78904..cd88b82 100644 --- a/src/service/rest-service.js +++ b/src/service/rest-service.js @@ -17,32 +17,26 @@ const axios = require('axios'); const bodyParser = require('body-parser'); -const Configstore = require('configstore'); const express = require('express'); const fs = require('fs'); const logger = require('winston'); -const path = require('path'); -const url = require('url'); const uuid = require('uuid'); const Errors = require('../utils/errors'); const Model = require('../model'); -const pkg = require('../../package.json'); const Service = require('./service'); const { CloudFunction, Operation } = Model; // TODO: Support more than one version. const API_VERSION = 'v1'; -const DISCOVERY_URL = `https://cloudfunctions.googleapis.com/$discovery/rest?version=${API_VERSION}`; class RestService extends Service { constructor (...args) { super(...args); this.type = 'REST'; - this._discovery = new Configstore(path.join(pkg.name, '/.discovery')); // Standard ExpressJS app. Where possible this should mimic the *actual* // setup of Cloud Functions regarding the use of body parsers etc. @@ -68,10 +62,6 @@ class RestService extends Service { }); this.server - .get( - `/([$])discovery/rest`, - (req, res, next) => this.getDiscoveryDoc(req, res).catch(next) - ) .delete( `/${API_VERSION}/projects/:project/locations/:location/functions/:name`, (req, res, next) => this.deleteFunction(req, res).catch(next) @@ -112,13 +102,6 @@ class RestService extends Service { * */ callFunction (req, res) { - if (req.headers['user-agent'].includes('google-cloud-sdk') && typeof req.body.data === 'string') { - try { - req.body.data = JSON.parse(req.body.data); - } catch (err) { - - } - } try { req.body.auth = JSON.parse(req.body.auth); } catch (err) { @@ -162,7 +145,7 @@ class RestService extends Service { .status(200) .send({ executionId: eventId, - result: response.body + result: response.data }) .end(); }, (err) => { @@ -170,7 +153,7 @@ class RestService extends Service { .status(200) .send({ executionId: eventId, - error: err.response ? err.response.body : err.message + error: err.response ? err.response.data : err.message }) .end(); }); @@ -223,51 +206,6 @@ class RestService extends Service { }); } - /** - * Gets the Google Cloud Functions API discovery doc. - * - * @param {object} req The request. - * @param {object} res The response. - */ - getDiscoveryDoc (req, res) { - return Promise.resolve() - .then(() => { - const doc = this._discovery.all; - if (typeof doc === 'object' && Object.keys(doc).length > 0 && doc.version === API_VERSION) { - return doc; - } - - return axios.request({ - url: DISCOVERY_URL, - method: 'GET', - params: { - version: req.query.version - } - }) - .then((response) => { - const doc = JSON.parse(response.body); - this._discovery.set(doc); - return doc; - }) - .catch((err) => { - if (err && err.statusCode === 404) { - return Promise.reject(new Errors.NotFoundError('Discovery document not found for API service.')); - } - return Promise.reject(err); - }); - }) - .then((doc) => { - // TODO: Change the baseUrl and rootUrl - doc.baseUrl = doc.rootUrl = url.format({ - hostname: this.config.host, - port: this.config.port, - protocol: `${req.protocol}:` - }) + '/'; - doc.canonicalName = 'Cloud Functions Emulator'; - res.status(200).json(doc).end(); - }); - } - /** * Gets a function. * @@ -283,16 +221,6 @@ class RestService extends Service { logger.debug('RestService#getFunction', name); return this.functions.getFunction(name) .then((cloudfunction) => { - if (req.get('user-agent') && - req.get('user-agent').includes('google-cloud-sdk') && - cloudfunction.status === 'DEPLOYING') { - // For some reason the Cloud SDK doesn't wait for the operation to be - // done before printing the function to the user, instead it polls for - // the function. So here pretend the function doesn't exist until it's - // no longer deploying. - return Promise.reject(this.functions._getFunctionNotFoundError(name)); - } - res.status(200).json(cloudfunction).end(); }); } @@ -352,7 +280,6 @@ class RestService extends Service { * @param {object} res The response. */ listFunctions (req, res) { - console.error('listFunctions'); const location = CloudFunction.formatLocation(req.params.project, req.params.location); logger.debug('RestService#listFunctions', location); return this.functions.listFunctions(location, { diff --git a/test/system/cli/index.test.js b/test/system/cli/index.test.js index 524b924..bc2a436 100644 --- a/test/system/cli/index.test.js +++ b/test/system/cli/index.test.js @@ -40,7 +40,6 @@ const config = new Configstore(path.join(pkg.name, `config`)); const server = new Configstore(path.join(pkg.name, `.active-server`)); const functions = new Configstore(path.join(pkg.name, `.functions`)); const operations = new Configstore(path.join(pkg.name, `.operations`)); -const prefix = `Google Cloud Functions Emulator`; const GCLOUD = process.env.GCLOUD_CMD_OVERRIDE || `gcloud`; const HOST = 'localhost'; @@ -95,12 +94,12 @@ function makeTests (service, override) { }) .then(() => tools.spawnAsyncWithIO('node', ['bin/functions', 'restart'], cwd)) .then((results) => { - assert(results.output.includes(`${prefix} STARTED`)); + assert(results.output.includes(`STARTED`)); return tools.tryTest(() => { return tools.spawnAsyncWithIO('node', ['bin/functions', 'clear'], cwd) .then((results) => { - assert(results.output.includes(`${prefix} CLEARED`)); + assert(results.output.includes(`CLEARED`)); }); }).start(); }); @@ -119,18 +118,18 @@ function makeTests (service, override) { }) .then(() => tools.spawnAsyncWithIO('node', ['bin/functions', 'restart'], cwd)) .then((results) => { - assert(results.output.includes(`${prefix} STARTED`)); + assert(results.output.includes(`STARTED`)); return tools.tryTest(() => { return tools.spawnAsyncWithIO('node', ['bin/functions', 'clear'], cwd) .then((results) => { - assert(results.output.includes(`${prefix} CLEARED`)); + assert(results.output.includes(`CLEARED`)); }); }).start(); }) .then(() => tools.spawnAsyncWithIO('node', ['bin/functions', 'stop'], cwd)) .then((results) => { - assert(results.output.includes(`${prefix} STOPPED`)); + assert(results.output.includes(`STOPPED`)); }); }); @@ -591,7 +590,7 @@ function makeTests (service, override) { assert(output.includes(`helloPOST`)); output = run(`${cmd} clear ${shortArgs}`, cwd); - assert(output.includes(`${prefix} CLEARED`)); + assert(output.includes(`CLEARED`)); output = run(`${cmd} list ${shortArgs}`, cwd); assert(output.includes(`No functions deployed`)); @@ -620,7 +619,6 @@ function makeTests (service, override) { describe(`status`, () => { it(`should show the server status`, () => { let output = run(`${cmd} status ${shortArgs}`, cwd); - assert(output.includes(`${prefix}`)); assert(output.includes(`RUNNING`)); assert(output.includes(`http://localhost:${REST_PORT}/`)); assert(output.includes(`http://localhost:${SUPERVISOR_PORT}/${PROJECT_ID}/${REGION}`)); @@ -658,5 +656,4 @@ describe(`system/cli`, () => { }); makeTests(`rest`); - makeTests(`rest`, `${GCLOUD} beta functions`); }); From 7b8eabac1bceffe1a6482b730d1939de31811081 Mon Sep 17 00:00:00 2001 From: Vitalii Tverdokhlib Date: Tue, 26 Feb 2019 07:33:20 +0200 Subject: [PATCH 3/3] fix circlecimv: cannot move '/opt/google-cloud-sdk': Permission denied (#300) --- .circleci/config.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 89f9f64..68fc522 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -61,9 +61,8 @@ jobs: - run: name: Install and configure Cloud SDK command: | - curl https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-225.0.0-linux-x86_64.tar.gz | tar xz - mv ./google-cloud-sdk /opt - export PATH=$PATH:/opt/google-cloud-sdk/bin + curl https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-228.0.0-linux-x86_64.tar.gz | tar xz -C /tmp + export PATH=$PATH:/tmp/google-cloud-sdk/bin gcloud components install beta -q gcloud components update -q gcloud config set project cloud-functions-emulator