From 287c4785009f99ef3c2fc30c21ced8e4c87fcae4 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Tue, 28 May 2019 07:13:53 -0700 Subject: [PATCH 01/25] Added stubs for add and remove func plugins --- src/index.ts | 4 +++ src/plugins/func/add/azureFuncAdd.ts | 30 ++++++++++++++++++++++ src/plugins/func/azureFunc.ts | 26 +++++++++++++++++++ src/plugins/func/remove/azureFuncRemove.ts | 30 ++++++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 src/plugins/func/add/azureFuncAdd.ts create mode 100644 src/plugins/func/azureFunc.ts create mode 100644 src/plugins/func/remove/azureFuncRemove.ts diff --git a/src/index.ts b/src/index.ts index 538f2c1c..bdf67d2d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,8 @@ import { AzureDeployPlugin } from './plugins/deploy/azureDeployPlugin'; import { AzureLoginPlugin } from './plugins/login/loginPlugin'; import { AzureApimServicePlugin } from './plugins/apim/apimServicePlugin'; import { AzureApimFunctionPlugin } from './plugins/apim/apimFunctionPlugin'; +import { AzureFuncPlugin } from './plugins/func/azureFunc'; +import { AzureFuncAddPlugin } from './plugins/func/add/azureFuncAdd'; export class AzureIndex { constructor(private serverless: Serverless, private options) { @@ -29,6 +31,8 @@ export class AzureIndex { this.serverless.pluginManager.addPlugin(AzureDeployPlugin); this.serverless.pluginManager.addPlugin(AzureApimServicePlugin); this.serverless.pluginManager.addPlugin(AzureApimFunctionPlugin); + this.serverless.pluginManager.addPlugin(AzureFuncPlugin); + this.serverless.pluginManager.addPlugin(AzureFuncAddPlugin); } } diff --git a/src/plugins/func/add/azureFuncAdd.ts b/src/plugins/func/add/azureFuncAdd.ts new file mode 100644 index 00000000..4688339b --- /dev/null +++ b/src/plugins/func/add/azureFuncAdd.ts @@ -0,0 +1,30 @@ +import Serverless from 'serverless'; + +export class AzureFuncAddPlugin { + public hooks: { [eventName: string]: Promise }; + public commands: any; + + constructor(private serverless: Serverless, private options: Serverless.Options) { + + this.commands = { + func: { + commands: { + add: { + usage: 'Add azure function', + lifecycleEvents: [ + 'add', + ] + } + } + } + } + + this.hooks = { + 'func:add:add': this.add.bind(this) + }; + } + + private async add() { + this.serverless.cli.log('Got to add'); + } +} \ No newline at end of file diff --git a/src/plugins/func/azureFunc.ts b/src/plugins/func/azureFunc.ts new file mode 100644 index 00000000..a8af15a3 --- /dev/null +++ b/src/plugins/func/azureFunc.ts @@ -0,0 +1,26 @@ +import Serverless from 'serverless'; + +export class AzureFuncPlugin { + public hooks: { [eventName: string]: Promise }; + public commands: any; + + + constructor(private serverless: Serverless, private options: Serverless.Options) { + this.hooks = { + 'func:func': this.func.bind(this), + }; + + this.commands = { + func: { + usage: 'Add or remove functions', + lifecycleEvents: [ + 'func', + ], + } + } + } + + private async func() { + this.serverless.cli.log('Got to func'); + } +} \ No newline at end of file diff --git a/src/plugins/func/remove/azureFuncRemove.ts b/src/plugins/func/remove/azureFuncRemove.ts new file mode 100644 index 00000000..9164cdb0 --- /dev/null +++ b/src/plugins/func/remove/azureFuncRemove.ts @@ -0,0 +1,30 @@ +import Serverless from 'serverless'; + +export class AzureFuncRemovePlugin { + public hooks: { [eventName: string]: Promise }; + public commands: any; + + constructor(private serverless: Serverless, private options: Serverless.Options) { + + this.commands = { + func: { + commands: { + remove: { + usage: 'Remove azure function', + lifecycleEvents: [ + 'remove', + ] + } + } + } + } + + this.hooks = { + 'func:remove:remove': this.remove.bind(this) + }; + } + + private async remove() { + this.serverless.cli.log('Got to remove'); + } +} \ No newline at end of file From da5d06c376362b1fde6ef80697d066ef9af8135d Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Tue, 28 May 2019 08:51:57 -0700 Subject: [PATCH 02/25] Removing from serverless yml and function directory --- package-lock.json | 10 ++---- package.json | 4 ++- src/index.ts | 3 ++ src/plugins/func/add/azureFuncAdd.ts | 1 + src/plugins/func/remove/azureFuncRemove.ts | 38 ++++++++++++++++++++-- 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 04d35d85..4e845153 100644 --- a/package-lock.json +++ b/package-lock.json @@ -997,7 +997,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -3985,7 +3984,6 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6058,7 +6056,6 @@ "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -6067,8 +6064,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" } } }, @@ -7986,7 +7982,6 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -8621,8 +8616,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.16.1", diff --git a/package.json b/package.json index 30eb685d..26412730 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,12 @@ "@azure/arm-resources": "^1.0.1", "@azure/ms-rest-nodeauth": "^1.0.1", "axios": "^0.18.0", + "js-yaml": "^3.13.1", "jsonpath": "^1.0.1", "lodash": "^4.16.6", "open": "^6.3.0", - "request": "^2.81.0" + "request": "^2.81.0", + "rimraf": "^2.6.3" }, "devDependencies": { "@types/jest": "^24.0.13", diff --git a/src/index.ts b/src/index.ts index bdf67d2d..377d6f87 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,8 @@ import { AzureApimServicePlugin } from './plugins/apim/apimServicePlugin'; import { AzureApimFunctionPlugin } from './plugins/apim/apimFunctionPlugin'; import { AzureFuncPlugin } from './plugins/func/azureFunc'; import { AzureFuncAddPlugin } from './plugins/func/add/azureFuncAdd'; +import { AzureFuncRemovePlugin } from './plugins/func/remove/azureFuncRemove'; + export class AzureIndex { constructor(private serverless: Serverless, private options) { @@ -33,6 +35,7 @@ export class AzureIndex { this.serverless.pluginManager.addPlugin(AzureApimFunctionPlugin); this.serverless.pluginManager.addPlugin(AzureFuncPlugin); this.serverless.pluginManager.addPlugin(AzureFuncAddPlugin); + this.serverless.pluginManager.addPlugin(AzureFuncRemovePlugin); } } diff --git a/src/plugins/func/add/azureFuncAdd.ts b/src/plugins/func/add/azureFuncAdd.ts index 4688339b..245eb517 100644 --- a/src/plugins/func/add/azureFuncAdd.ts +++ b/src/plugins/func/add/azureFuncAdd.ts @@ -26,5 +26,6 @@ export class AzureFuncAddPlugin { private async add() { this.serverless.cli.log('Got to add'); + this.serverless.cli.log(JSON.stringify(this.options)); } } \ No newline at end of file diff --git a/src/plugins/func/remove/azureFuncRemove.ts b/src/plugins/func/remove/azureFuncRemove.ts index 9164cdb0..11b3ec80 100644 --- a/src/plugins/func/remove/azureFuncRemove.ts +++ b/src/plugins/func/remove/azureFuncRemove.ts @@ -1,3 +1,6 @@ +import fs from 'fs'; +import yaml from 'js-yaml'; +import rimraf from 'rimraf'; import Serverless from 'serverless'; export class AzureFuncRemovePlugin { @@ -13,7 +16,13 @@ export class AzureFuncRemovePlugin { usage: 'Remove azure function', lifecycleEvents: [ 'remove', - ] + ], + options: { + name: { + usage: 'Name of function to remove', + shortcut: 'n', + } + } } } } @@ -25,6 +34,31 @@ export class AzureFuncRemovePlugin { } private async remove() { - this.serverless.cli.log('Got to remove'); + if (!('name' in this.options)) { + this.serverless.cli.log('Need to provide a name of function to remove'); + return; + } + const funcToRemove = this.options['name']; + const exists = fs.existsSync(funcToRemove); + if (!exists) { + this.serverless.cli.log(`Function ${funcToRemove} does not exist`); + return; + } + this.serverless.cli.log(`Removing ${funcToRemove}`); + rimraf.sync(funcToRemove); + await this.removeFromServerlessYml(funcToRemove); + } + + private async removeFromServerlessYml(name: string) { + const serverlessYml = fs.readFileSync('serverless.yml', 'utf-8'); + + const functionsRegex = /functions:([\s\S]*?)\n\n/g + const functionsSection = serverlessYml.match(functionsRegex)[0]; + + const parsed = yaml.safeLoad(functionsSection); + delete parsed['functions'][name]; + const newFunctionsYaml = yaml.dump(parsed); + const newServerlessYaml = serverlessYml.replace(functionsRegex, `${newFunctionsYaml}\n\n`); + fs.writeFileSync('serverless.yml', newServerlessYaml); } } \ No newline at end of file From 544accf8dd8303fcf43b3002a5132956ab9a0d0b Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Tue, 28 May 2019 09:54:22 -0700 Subject: [PATCH 03/25] Generating scaffolding code with add function --- src/plugins/func/add/azureFuncAdd.ts | 41 +++++++++- src/plugins/func/funcUtils.ts | 95 ++++++++++++++++++++++ src/plugins/func/remove/azureFuncRemove.ts | 15 +--- 3 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 src/plugins/func/funcUtils.ts diff --git a/src/plugins/func/add/azureFuncAdd.ts b/src/plugins/func/add/azureFuncAdd.ts index 245eb517..c0cf5d7e 100644 --- a/src/plugins/func/add/azureFuncAdd.ts +++ b/src/plugins/func/add/azureFuncAdd.ts @@ -1,4 +1,9 @@ import Serverless from 'serverless'; +import fs from 'fs'; +import yaml from 'js-yaml'; +import path from 'path'; +import { FuncPluginUtils } from '../funcUtils' + export class AzureFuncAddPlugin { public hooks: { [eventName: string]: Promise }; @@ -13,7 +18,13 @@ export class AzureFuncAddPlugin { usage: 'Add azure function', lifecycleEvents: [ 'add', - ] + ], + options: { + name: { + usage: 'Name of function to add', + shortcut: 'n', + } + } } } } @@ -25,7 +36,31 @@ export class AzureFuncAddPlugin { } private async add() { - this.serverless.cli.log('Got to add'); - this.serverless.cli.log(JSON.stringify(this.options)); + if (!('name' in this.options)) { + this.serverless.cli.log('Need to provide a name of function to add'); + return; + } + const funcToAdd = this.options['name'] + const exists = fs.existsSync(funcToAdd); + if (exists) { + this.serverless.cli.log(`Function ${funcToAdd} already exists`); + return; + } + this.createFunctionDir(funcToAdd); + this.addToServerlessYml(funcToAdd); } + + private createFunctionDir(name: string) { + this.serverless.cli.log('Creating function dir'); + fs.mkdirSync(name); + fs.writeFileSync(path.join(name, 'index.js'), FuncPluginUtils.getFunctionHandler(name)); + fs.writeFileSync(path.join(name, 'function.json'), FuncPluginUtils.getFunctionJson(name, this.options)) + } + + private addToServerlessYml(name: string) { + this.serverless.cli.log('Adding to serverless.yml'); + const functionYml = FuncPluginUtils.getFunctionsYml(); + functionYml.functions[name] = FuncPluginUtils.getFunctionSlsObject(name, this.options); + FuncPluginUtils.updateFunctionsYml(functionYml); + } } \ No newline at end of file diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts new file mode 100644 index 00000000..e471738f --- /dev/null +++ b/src/plugins/func/funcUtils.ts @@ -0,0 +1,95 @@ +import yaml from 'js-yaml'; +import fs from 'fs'; + +const functionsRegex = /functions:([\s\S]*?)\n\n/g + +export class FuncPluginUtils { + + public static getFunctionsYml() { + const serverlessYml = fs.readFileSync('serverless.yml', 'utf-8'); + const functionsSection = serverlessYml.match(functionsRegex)[0]; + return yaml.safeLoad(functionsSection); + } + + public static updateFunctionsYml(functionYml: any) { + const serverlessYml = fs.readFileSync('serverless.yml', 'utf-8'); + const newFunctionsYaml = yaml.dump(functionYml); + const newServerlessYaml = serverlessYml.replace(functionsRegex, `${newFunctionsYaml}\n`); + fs.writeFileSync('serverless.yml', newServerlessYaml); + } + + public static getFunctionHandler(name: string) { + return `'use strict'; + +module.exports.handler = async function (context, req) { + context.log('JavaScript HTTP trigger function processed a request.'); + + if (req.query.name || (req.body && req.body.name)) { + context.res = { + // status: 200, /* Defaults to 200 */ + body: "${name} " + (req.query.name || req.body.name) + }; + } + else { + context.res = { + status: 400, + body: "Please pass a name on the query string or in the request body" + }; + } +};`; + } + + public static getFunctionJson(name: string, options: any) { + // TODO: This is where we would just generate function JSON from SLS object + // using getFunctionSlsObject(name, options). Currently defaulting to http in and out + return JSON.stringify({ + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "res" + } + ] + }); + } + + public static getFunctionSlsObject(name: string, options: any) { + + return FuncPluginUtils.defaultFunctionSlsObject(name); + } + + private static defaultFunctionSlsObject(name: string) { + return { + handler: `${name}/index.handler`, + events: FuncPluginUtils.httpEvents() + } + } + + private static httpEvents() { + return [ + { + http: true, + 'x-azure-settings': { + authLevel: 'anonymous' + } + }, + { + http: true, + 'x-azure-settings': { + direction: 'out', + name: 'res' + } + }, + ] + } +} \ No newline at end of file diff --git a/src/plugins/func/remove/azureFuncRemove.ts b/src/plugins/func/remove/azureFuncRemove.ts index 11b3ec80..3d8d4674 100644 --- a/src/plugins/func/remove/azureFuncRemove.ts +++ b/src/plugins/func/remove/azureFuncRemove.ts @@ -1,7 +1,7 @@ import fs from 'fs'; -import yaml from 'js-yaml'; import rimraf from 'rimraf'; import Serverless from 'serverless'; +import { FuncPluginUtils } from '../funcUtils' export class AzureFuncRemovePlugin { public hooks: { [eventName: string]: Promise }; @@ -50,15 +50,8 @@ export class AzureFuncRemovePlugin { } private async removeFromServerlessYml(name: string) { - const serverlessYml = fs.readFileSync('serverless.yml', 'utf-8'); - - const functionsRegex = /functions:([\s\S]*?)\n\n/g - const functionsSection = serverlessYml.match(functionsRegex)[0]; - - const parsed = yaml.safeLoad(functionsSection); - delete parsed['functions'][name]; - const newFunctionsYaml = yaml.dump(parsed); - const newServerlessYaml = serverlessYml.replace(functionsRegex, `${newFunctionsYaml}\n\n`); - fs.writeFileSync('serverless.yml', newServerlessYaml); + const functionYml = FuncPluginUtils.getFunctionsYml(); + delete functionYml.functions[name]; + FuncPluginUtils.updateFunctionsYml(functionYml) } } \ No newline at end of file From 7e2255303c6dabd253b8139e8da91c50a888f5f3 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Tue, 28 May 2019 14:35:08 -0700 Subject: [PATCH 04/25] Mocking file system --- package-lock.json | 6 +++ package.json | 1 + src/plugins/func/funcUtils.test.ts | 24 ++++++++++ src/plugins/func/funcUtils.ts | 10 +++-- src/test/mockFactory.ts | 43 ++++++++++++++++++ src/test/sampleData.ts | 70 ++++++++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 src/plugins/func/funcUtils.test.ts create mode 100644 src/test/sampleData.ts diff --git a/package-lock.json b/package-lock.json index 4e845153..679c8e26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6772,6 +6772,12 @@ } } }, + "mock-fs": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.10.0.tgz", + "integrity": "sha512-eBpLEjI6tK4RKK44BbUBQu89lrNh+5WeX3wf2U6Uwo6RtRGAQ77qvKeuuQh3lVXHF1aPndVww9VcjqmLThIdtA==", + "dev": true + }, "moment": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", diff --git a/package.json b/package.json index 26412730..b038e446 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "eslint": "^5.16.0", "jest": "^24.8.0", "jest-cli": "^24.8.0", + "mock-fs": "^4.10.0", "serverless": "^1.43.0", "ts-jest": "^24.0.2", "typescript": "^3.4.5" diff --git a/src/plugins/func/funcUtils.test.ts b/src/plugins/func/funcUtils.test.ts new file mode 100644 index 00000000..257ffb85 --- /dev/null +++ b/src/plugins/func/funcUtils.test.ts @@ -0,0 +1,24 @@ +import { MockFactory } from '../../test/mockFactory'; +import { serverlessYmlString } from '../../test/sampleData'; +import { FuncPluginUtils } from './funcUtils'; +import fs from 'fs'; +import mock from 'mock-fs' + +describe('Func Utils', () => { + + beforeAll(() => { + mock({ + 'serverless.yml': serverlessYmlString + }, {createCwd: true, createTmp: true}) + }); + + afterAll(() => { + mock.restore(); + }) + + + it('gets functions yml', () => { + const sls = FuncPluginUtils.getServerlessYml(); + expect(FuncPluginUtils.getFunctionsYml(serverlessYmlString)).toEqual(MockFactory.createTestFunctionsMetadata()); + }); +}); \ No newline at end of file diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts index e471738f..30f47ef9 100644 --- a/src/plugins/func/funcUtils.ts +++ b/src/plugins/func/funcUtils.ts @@ -5,14 +5,16 @@ const functionsRegex = /functions:([\s\S]*?)\n\n/g export class FuncPluginUtils { - public static getFunctionsYml() { - const serverlessYml = fs.readFileSync('serverless.yml', 'utf-8'); + public static getServerlessYml() { + return fs.readFileSync('serverless.yml', 'utf-8'); + } + + public static getFunctionsYml(serverlessYml: string) { const functionsSection = serverlessYml.match(functionsRegex)[0]; return yaml.safeLoad(functionsSection); } - public static updateFunctionsYml(functionYml: any) { - const serverlessYml = fs.readFileSync('serverless.yml', 'utf-8'); + public static updateFunctionsYml(serverlessYml: string, functionYml: any) { const newFunctionsYaml = yaml.dump(functionYml); const newServerlessYaml = serverlessYml.replace(functionsRegex, `${newFunctionsYaml}\n`); fs.writeFileSync('serverless.yml', newServerlessYaml); diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index da96e62f..e1312e17 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -37,6 +37,49 @@ export class MockFactory { } } + public static createTestFunctionsMetadata() { + return { + 'functions': { + 'hello': { + 'handler': 'hello/index.handler', + 'events': [ + { + 'http': true, + 'x-azure-settings': { + 'authLevel': 'anonymous' + } + }, + { + 'http': true, + 'x-azure-settings': { + 'direction': 'out', + 'name': 'res' + } + } + ] + }, + 'goodbye': { + 'handler': 'goodbye/index.handler', + 'events': [ + { + 'http': true, + 'x-azure-settings': { + 'authLevel': 'anonymous' + } + }, + { + 'http': true, + 'x-azure-settings': { + 'direction': 'out', + 'name': 'res' + } + } + ] + } + } + } + } + private static createTestService(): Service { return { getAllFunctions: jest.fn(() => ['function1']), diff --git a/src/test/sampleData.ts b/src/test/sampleData.ts new file mode 100644 index 00000000..f12298a6 --- /dev/null +++ b/src/test/sampleData.ts @@ -0,0 +1,70 @@ +export const serverlessYmlString = `# Welcome to Serverless! +# +# This file is the main config file for your service. +# It's very minimal at this point and uses default values. +# You can always add more config options for more control. +# We've included some commented out config examples here. +# Just uncomment any of them to get that config option. +# +# For full config options, check the docs: +# docs.serverless.com +# +# Happy Coding! + +service: local-app # NOTE: update this with your service name + +# You can pin your service to only deploy with a specific Serverless version +# Check out our docs for more details +# frameworkVersion: "=X.X.X" + +provider: + name: azure + location: West US 2 + +plugins: + - serverless-azure-functions + +# you can add packaging information here +package: +# include: +# - include-me.js +# - include-me-dir/** + exclude: +# - exclude-me.js +# - exclude-me-dir/** + - local.settings.json + - .vscode/** + +functions: + hello: + handler: hello/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res + goodbye: + handler: goodbye/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res + +# The following are a few examples of other events you can configure: +# +# events: +# - queue: YourQueueName +# x-azure-settings: +# connection : StorageAppSettingName +# - blob: +# x-azure-settings: +# name: bindingName +# direction: in +` \ No newline at end of file From 6882d735cb45d9c2c1b99d65a6a8e6f963bfbdab Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Tue, 28 May 2019 15:04:15 -0700 Subject: [PATCH 05/25] Stub tests for func utils --- src/plugins/func/funcUtils.test.ts | 13 +++- src/plugins/func/funcUtils.ts | 6 +- src/test/sampleData.ts | 116 +++++++++++++++++++---------- 3 files changed, 89 insertions(+), 46 deletions(-) diff --git a/src/plugins/func/funcUtils.test.ts b/src/plugins/func/funcUtils.test.ts index 257ffb85..bc8f2202 100644 --- a/src/plugins/func/funcUtils.test.ts +++ b/src/plugins/func/funcUtils.test.ts @@ -1,5 +1,5 @@ import { MockFactory } from '../../test/mockFactory'; -import { serverlessYmlString } from '../../test/sampleData'; +import { originalSlsYml, additionalFunctionSlsYml, additionalFunctionYml } from '../../test/sampleData'; import { FuncPluginUtils } from './funcUtils'; import fs from 'fs'; import mock from 'mock-fs' @@ -8,8 +8,9 @@ describe('Func Utils', () => { beforeAll(() => { mock({ - 'serverless.yml': serverlessYmlString + 'serverless.yml': originalSlsYml }, {createCwd: true, createTmp: true}) + fs.writeFileSync = jest.fn(); }); afterAll(() => { @@ -19,6 +20,12 @@ describe('Func Utils', () => { it('gets functions yml', () => { const sls = FuncPluginUtils.getServerlessYml(); - expect(FuncPluginUtils.getFunctionsYml(serverlessYmlString)).toEqual(MockFactory.createTestFunctionsMetadata()); + expect(FuncPluginUtils.getFunctionsYml(originalSlsYml)).toEqual( + MockFactory.createTestFunctionsMetadata()); + }); + + it('updates functions yml', () => { + FuncPluginUtils.updateFunctionsYml(additionalFunctionYml, originalSlsYml); + expect(fs.writeFileSync).toBeCalledWith('serverless.yml', additionalFunctionSlsYml); }); }); \ No newline at end of file diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts index 30f47ef9..b622e0f8 100644 --- a/src/plugins/func/funcUtils.ts +++ b/src/plugins/func/funcUtils.ts @@ -9,12 +9,14 @@ export class FuncPluginUtils { return fs.readFileSync('serverless.yml', 'utf-8'); } - public static getFunctionsYml(serverlessYml: string) { + public static getFunctionsYml(serverlessYml?: string) { + serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(); const functionsSection = serverlessYml.match(functionsRegex)[0]; return yaml.safeLoad(functionsSection); } - public static updateFunctionsYml(serverlessYml: string, functionYml: any) { + public static updateFunctionsYml(functionYml: any, serverlessYml: string) { + serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(); const newFunctionsYaml = yaml.dump(functionYml); const newServerlessYaml = serverlessYml.replace(functionsRegex, `${newFunctionsYaml}\n`); fs.writeFileSync('serverless.yml', newServerlessYaml); diff --git a/src/test/sampleData.ts b/src/test/sampleData.ts index f12298a6..1a2ea0e8 100644 --- a/src/test/sampleData.ts +++ b/src/test/sampleData.ts @@ -1,40 +1,75 @@ -export const serverlessYmlString = `# Welcome to Serverless! -# -# This file is the main config file for your service. -# It's very minimal at this point and uses default values. -# You can always add more config options for more control. -# We've included some commented out config examples here. -# Just uncomment any of them to get that config option. -# -# For full config options, check the docs: -# docs.serverless.com -# -# Happy Coding! +export const originalSlsYml = `provider: +name: azure +location: West US 2 + +plugins: + - serverless-azure-functions + +functions: + hello: + handler: hello/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res + goodbye: + handler: goodbye/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res +` + +export const additionalFunctionYml = `functions: + hello: + handler: hello/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res + goodbye: + handler: goodbye/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res + greetings: + handler: greetings/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res -service: local-app # NOTE: update this with your service name -# You can pin your service to only deploy with a specific Serverless version -# Check out our docs for more details -# frameworkVersion: "=X.X.X" +` -provider: - name: azure - location: West US 2 +export const additionalFunctionSlsYml = `provider: +name: azure +location: West US 2 plugins: - serverless-azure-functions -# you can add packaging information here -package: -# include: -# - include-me.js -# - include-me-dir/** - exclude: -# - exclude-me.js -# - exclude-me-dir/** - - local.settings.json - - .vscode/** - functions: hello: handler: hello/index.handler @@ -56,15 +91,14 @@ functions: x-azure-settings: direction: out name: res - -# The following are a few examples of other events you can configure: -# -# events: -# - queue: YourQueueName -# x-azure-settings: -# connection : StorageAppSettingName -# - blob: -# x-azure-settings: -# name: bindingName -# direction: in + greetings: + handler: greetings/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res ` \ No newline at end of file From 4b01f4c25d074c8c2408aa03f3b4d54e2162be49 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Wed, 29 May 2019 08:29:11 -0700 Subject: [PATCH 06/25] Added tests for add and remove plugins --- myExistingFunction/function.json | 1 + myExistingFunction/index.js | 18 +++ myFunction/function.json | 1 + myFunction/index.js | 18 +++ src/plugins/func/add/azureFuncAdd.test.ts | 60 +++++++++ src/plugins/func/funcUtils.test.ts | 32 +++-- src/plugins/func/funcUtils.ts | 2 +- .../func/remove/azureFuncRemove.test.ts | 55 +++++++++ src/plugins/func/remove/azureFuncRemove.ts | 2 +- src/test/mockFactory.ts | 77 ++++++------ src/test/sampleData.ts | 115 +++++++----------- 11 files changed, 261 insertions(+), 120 deletions(-) create mode 100644 myExistingFunction/function.json create mode 100644 myExistingFunction/index.js create mode 100644 myFunction/function.json create mode 100644 myFunction/index.js create mode 100644 src/plugins/func/add/azureFuncAdd.test.ts create mode 100644 src/plugins/func/remove/azureFuncRemove.test.ts diff --git a/myExistingFunction/function.json b/myExistingFunction/function.json new file mode 100644 index 00000000..73b3077d --- /dev/null +++ b/myExistingFunction/function.json @@ -0,0 +1 @@ +{"bindings":[{"authLevel":"anonymous","type":"httpTrigger","direction":"in","name":"req","methods":["get","post"]},{"type":"http","direction":"out","name":"res"}]} \ No newline at end of file diff --git a/myExistingFunction/index.js b/myExistingFunction/index.js new file mode 100644 index 00000000..77df3ac6 --- /dev/null +++ b/myExistingFunction/index.js @@ -0,0 +1,18 @@ +'use strict'; + +module.exports.handler = async function (context, req) { + context.log('JavaScript HTTP trigger function processed a request.'); + + if (req.query.name || (req.body && req.body.name)) { + context.res = { + // status: 200, /* Defaults to 200 */ + body: "myExistingFunction " + (req.query.name || req.body.name) + }; + } + else { + context.res = { + status: 400, + body: "Please pass a name on the query string or in the request body" + }; + } +}; \ No newline at end of file diff --git a/myFunction/function.json b/myFunction/function.json new file mode 100644 index 00000000..73b3077d --- /dev/null +++ b/myFunction/function.json @@ -0,0 +1 @@ +{"bindings":[{"authLevel":"anonymous","type":"httpTrigger","direction":"in","name":"req","methods":["get","post"]},{"type":"http","direction":"out","name":"res"}]} \ No newline at end of file diff --git a/myFunction/index.js b/myFunction/index.js new file mode 100644 index 00000000..8807522b --- /dev/null +++ b/myFunction/index.js @@ -0,0 +1,18 @@ +'use strict'; + +module.exports.handler = async function (context, req) { + context.log('JavaScript HTTP trigger function processed a request.'); + + if (req.query.name || (req.body && req.body.name)) { + context.res = { + // status: 200, /* Defaults to 200 */ + body: "myFunction " + (req.query.name || req.body.name) + }; + } + else { + context.res = { + status: 400, + body: "Please pass a name on the query string or in the request body" + }; + } +}; \ No newline at end of file diff --git a/src/plugins/func/add/azureFuncAdd.test.ts b/src/plugins/func/add/azureFuncAdd.test.ts new file mode 100644 index 00000000..2c75e2f0 --- /dev/null +++ b/src/plugins/func/add/azureFuncAdd.test.ts @@ -0,0 +1,60 @@ +import fs from 'fs'; +import mock from 'mock-fs'; +import path from 'path'; +import { MockFactory } from '../../../test/mockFactory'; +import { invokeHook } from "../../../test/utils"; +import { AzureFuncAddPlugin } from './azureFuncAdd'; + +describe('Azure Func Add', () => { + + beforeAll(() => { + mock({ + 'myExistingFunction': { + 'index.js': 'contents', + 'function.json': 'contents', + }, + 'serverless.yml': MockFactory.createTestServerlessYml(true) + }, {createCwd: true, createTmp: true}) + fs.writeFileSync = jest.fn(); + fs.mkdirSync = jest.fn(); + }); + + afterAll(() => { + mock.restore(); + }) + + it('returns with missing name', async () => { + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + const plugin = new AzureFuncAddPlugin(sls, options); + await invokeHook(plugin, 'func:add:add'); + expect(sls.cli.log).toBeCalledWith('Need to provide a name of function to add') + }); + + it('returns with pre-existing function', async () => { + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + options['name'] = 'myExistingFunction'; + const plugin = new AzureFuncAddPlugin(sls, options); + await invokeHook(plugin, 'func:add:add'); + expect(sls.cli.log).toBeCalledWith(`Function myExistingFunction already exists`); + }); + + it('creates function directory and updates serverless.yml', async () => { + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + const plugin = new AzureFuncAddPlugin(sls, options); + const functionName = 'myFunction'; + options['name'] = functionName; + await invokeHook(plugin, 'func:add:add'); + expect(fs.mkdirSync).toBeCalledWith(functionName); + const calls = (fs.writeFileSync as any).mock.calls; + expect(calls[0][0]).toBe(path.join(functionName, 'index.js')); + expect(calls[1][0]).toBe(path.join(functionName, 'function.json')); + + const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); + expectedFunctionsYml[functionName] = MockFactory.createTestFunctionMetadata(functionName); + expect(calls[2][0]).toBe('serverless.yml'); + expect(calls[2][1]).toBe(MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) + }); +}); \ No newline at end of file diff --git a/src/plugins/func/funcUtils.test.ts b/src/plugins/func/funcUtils.test.ts index bc8f2202..b24eada9 100644 --- a/src/plugins/func/funcUtils.test.ts +++ b/src/plugins/func/funcUtils.test.ts @@ -1,14 +1,13 @@ +import fs from 'fs'; +import mock from 'mock-fs'; import { MockFactory } from '../../test/mockFactory'; -import { originalSlsYml, additionalFunctionSlsYml, additionalFunctionYml } from '../../test/sampleData'; import { FuncPluginUtils } from './funcUtils'; -import fs from 'fs'; -import mock from 'mock-fs' describe('Func Utils', () => { beforeAll(() => { mock({ - 'serverless.yml': originalSlsYml + 'serverless.yml': MockFactory.createTestServerlessYml(true) }, {createCwd: true, createTmp: true}) fs.writeFileSync = jest.fn(); }); @@ -19,13 +18,28 @@ describe('Func Utils', () => { it('gets functions yml', () => { - const sls = FuncPluginUtils.getServerlessYml(); - expect(FuncPluginUtils.getFunctionsYml(originalSlsYml)).toEqual( - MockFactory.createTestFunctionsMetadata()); + const funcYaml = FuncPluginUtils.getFunctionsYml(); + const expected = { + 'functions': MockFactory.createTestFunctionsMetadata() + } + expect(funcYaml).toEqual(expected); }); it('updates functions yml', () => { - FuncPluginUtils.updateFunctionsYml(additionalFunctionYml, originalSlsYml); - expect(fs.writeFileSync).toBeCalledWith('serverless.yml', additionalFunctionSlsYml); + FuncPluginUtils.updateFunctionsYml( + MockFactory.createTestFunctionsMetadata(3, true), + MockFactory.createTestServerlessYml(true, 2) as string); + const call = (fs.writeFileSync as any).mock.calls[0] + expect(call[0]).toBe('serverless.yml'); + expect(call[1]).toBe(MockFactory.createTestServerlessYml( + true, + MockFactory.createTestFunctionsMetadata(3) + )); + }); + + it('adds new function name to function handler', () => { + const name = 'This is my function name' + expect(FuncPluginUtils.getFunctionHandler(name)) + .toContain(`body: "${name} " + (req.query.name || req.body.name)`) }); }); \ No newline at end of file diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts index b622e0f8..ac623c07 100644 --- a/src/plugins/func/funcUtils.ts +++ b/src/plugins/func/funcUtils.ts @@ -15,7 +15,7 @@ export class FuncPluginUtils { return yaml.safeLoad(functionsSection); } - public static updateFunctionsYml(functionYml: any, serverlessYml: string) { + public static updateFunctionsYml(functionYml: any, serverlessYml?: string) { serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(); const newFunctionsYaml = yaml.dump(functionYml); const newServerlessYaml = serverlessYml.replace(functionsRegex, `${newFunctionsYaml}\n`); diff --git a/src/plugins/func/remove/azureFuncRemove.test.ts b/src/plugins/func/remove/azureFuncRemove.test.ts new file mode 100644 index 00000000..b311b8fd --- /dev/null +++ b/src/plugins/func/remove/azureFuncRemove.test.ts @@ -0,0 +1,55 @@ +import fs from 'fs'; +import mock from 'mock-fs'; +import rimraf from 'rimraf'; +import { MockFactory } from '../../../test/mockFactory'; +import { invokeHook } from "../../../test/utils"; +import { AzureFuncRemovePlugin } from './azureFuncRemove'; + +describe('Azure Func Add', () => { + + beforeAll(() => { + mock({ + 'function1': { + 'index.js': 'contents', + 'function.json': 'contents', + }, + 'serverless.yml': MockFactory.createTestServerlessYml(true) + }, {createCwd: true, createTmp: true}) + fs.writeFileSync = jest.fn(); + rimraf.sync = jest.fn(); + }); + + afterAll(() => { + mock.restore(); + }) + + it('returns with missing name', async () => { + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + const plugin = new AzureFuncRemovePlugin(sls, options); + await invokeHook(plugin, 'func:remove:remove'); + expect(sls.cli.log).toBeCalledWith('Need to provide a name of function to remove') + }); + + it('returns with pre-existing function', async () => { + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + options['name'] = 'myNonExistingFunction'; + const plugin = new AzureFuncRemovePlugin(sls, options); + await invokeHook(plugin, 'func:remove:remove'); + expect(sls.cli.log).toBeCalledWith(`Function myNonExistingFunction does not exist`); + }); + + it('deletes directory and updates serverless.yml', async () => { + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + const plugin = new AzureFuncRemovePlugin(sls, options); + const functionName = 'function1'; + options['name'] = functionName; + await invokeHook(plugin, 'func:remove:remove'); + expect(rimraf.sync).toBeCalledWith(functionName); + const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); + delete expectedFunctionsYml[functionName]; + expect(fs.writeFileSync).toBeCalledWith('serverless.yml', MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) + }); +}); \ No newline at end of file diff --git a/src/plugins/func/remove/azureFuncRemove.ts b/src/plugins/func/remove/azureFuncRemove.ts index 3d8d4674..c85b9664 100644 --- a/src/plugins/func/remove/azureFuncRemove.ts +++ b/src/plugins/func/remove/azureFuncRemove.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import rimraf from 'rimraf'; import Serverless from 'serverless'; -import { FuncPluginUtils } from '../funcUtils' +import { FuncPluginUtils } from '../funcUtils'; export class AzureFuncRemovePlugin { public hooks: { [eventName: string]: Promise }; diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index e1312e17..0fbc87fe 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -3,6 +3,8 @@ import Serverless from 'serverless'; import Service from 'serverless/classes/Service'; import Utils = require('serverless/classes/Utils'); import PluginManager = require('serverless/classes/PluginManager'); +import yaml from 'js-yaml' + export class MockFactory { public static createTestServerless(config?: any): Serverless { @@ -37,46 +39,47 @@ export class MockFactory { } } - public static createTestFunctionsMetadata() { + public static createTestServerlessYml(asYaml = false, functionMetadata?) { + const data = { + 'provider': { + 'name': 'azure', + 'location': 'West US 2' + }, + 'plugins': [ + 'serverless-azure-functions' + ], + 'functions': functionMetadata || MockFactory.createTestFunctionsMetadata(), + } + return (asYaml) ? yaml.dump(data) + '\n' : data; + } + + public static createTestFunctionsMetadata(functionCount = 2, wrap = false) { + const data = {}; + for (let i = 0; i < functionCount; i++) { + const functionName = `function${i+1}`; + data[functionName] = MockFactory.createTestFunctionMetadata(functionName) + } + return (wrap) ? {'functions': data } : data; + } + + public static createTestFunctionMetadata(name: string) { return { - 'functions': { - 'hello': { - 'handler': 'hello/index.handler', - 'events': [ - { - 'http': true, - 'x-azure-settings': { - 'authLevel': 'anonymous' - } - }, - { - 'http': true, - 'x-azure-settings': { - 'direction': 'out', - 'name': 'res' - } - } - ] + 'handler': `${name}/index.handler`, + 'events': [ + { + 'http': true, + 'x-azure-settings': { + 'authLevel': 'anonymous' + } }, - 'goodbye': { - 'handler': 'goodbye/index.handler', - 'events': [ - { - 'http': true, - 'x-azure-settings': { - 'authLevel': 'anonymous' - } - }, - { - 'http': true, - 'x-azure-settings': { - 'direction': 'out', - 'name': 'res' - } - } - ] + { + 'http': true, + 'x-azure-settings': { + 'direction': 'out', + 'name': 'res' + } } - } + ] } } diff --git a/src/test/sampleData.ts b/src/test/sampleData.ts index 1a2ea0e8..1c35a500 100644 --- a/src/test/sampleData.ts +++ b/src/test/sampleData.ts @@ -1,11 +1,4 @@ -export const originalSlsYml = `provider: -name: azure -location: West US 2 - -plugins: - - serverless-azure-functions - -functions: +export const originalFunctionsYml = `functions: hello: handler: hello/index.handler events: @@ -25,44 +18,51 @@ functions: - http: true x-azure-settings: direction: out - name: res -` + name: res` -export const additionalFunctionYml = `functions: - hello: - handler: hello/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res - goodbye: - handler: goodbye/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res - greetings: - handler: greetings/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res +export const originalSlsYml = `provider: +name: azure +location: West US 2 + +plugins: + - serverless-azure-functions +${originalFunctionsYml} ` +export const additionalFunctionYml = `functions: +hello: + handler: hello/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res +goodbye: + handler: goodbye/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res +greetings: + handler: greetings/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res` + export const additionalFunctionSlsYml = `provider: name: azure location: West US 2 @@ -70,35 +70,6 @@ location: West US 2 plugins: - serverless-azure-functions -functions: - hello: - handler: hello/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res - goodbye: - handler: goodbye/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res - greetings: - handler: greetings/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res +${additionalFunctionYml} + ` \ No newline at end of file From ffebc303f625286993a4dd2d320ac3c2e41fac18 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Wed, 29 May 2019 08:29:38 -0700 Subject: [PATCH 07/25] Remove sample data --- src/test/sampleData.ts | 75 ------------------------------------------ 1 file changed, 75 deletions(-) delete mode 100644 src/test/sampleData.ts diff --git a/src/test/sampleData.ts b/src/test/sampleData.ts deleted file mode 100644 index 1c35a500..00000000 --- a/src/test/sampleData.ts +++ /dev/null @@ -1,75 +0,0 @@ -export const originalFunctionsYml = `functions: - hello: - handler: hello/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res - goodbye: - handler: goodbye/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res` - -export const originalSlsYml = `provider: -name: azure -location: West US 2 - -plugins: - - serverless-azure-functions - -${originalFunctionsYml} - -` - -export const additionalFunctionYml = `functions: -hello: - handler: hello/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res -goodbye: - handler: goodbye/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res -greetings: - handler: greetings/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res` - -export const additionalFunctionSlsYml = `provider: -name: azure -location: West US 2 - -plugins: - - serverless-azure-functions - -${additionalFunctionYml} - -` \ No newline at end of file From 6a74d86146358a2ebf7bacff5179c40d72a0c9d5 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Wed, 29 May 2019 08:35:24 -0700 Subject: [PATCH 08/25] Add docs --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 310a693b..1535a58a 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,26 @@ This plugin enables Azure Functions support within the Serverless Framework. 2. CD into the generated app directory: `cd ` 3. Install the app's NPM dependencies, which includes this plugin: `npm install` +### Creating or removing Azure Functions + +To create a new Azure Function within your function app, run the following command from within your app's directory: + +```bash +sls func add -n {functionName} +``` + +This will create a new `{functionName}` directory at the root of your application with `index.js` and `function.json` inside the directory. It will also update `serverless.yml` to contain the new function. + +To remove an existing Azure Function from your function app, run the following command from within your app's directory: + +```bash +sls func remove -n {functionName} +``` + +This will remove the `{functionName}` directory and remove the function from `serverless.yml` + +*Note: Add & remove currently only support HTTP triggered functions. For other triggers, you will need to update `serverless.yml` manually + ### Deploy, test, and diagnose your Azure service 1. Deploy your new service to Azure! The first time you do this, you will be asked to authenticate with your Azure account, so the `serverless` CLI can manage Functions on your behalf. Simply follow the provided instructions, and the deployment will continue as soon as the authentication process is completed. From 552fce271072bdd08653553d8b986062012bd662 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Wed, 29 May 2019 08:36:23 -0700 Subject: [PATCH 09/25] Remove junk files from test --- myExistingFunction/function.json | 1 - myExistingFunction/index.js | 18 ------------------ myFunction/function.json | 1 - myFunction/index.js | 18 ------------------ 4 files changed, 38 deletions(-) delete mode 100644 myExistingFunction/function.json delete mode 100644 myExistingFunction/index.js delete mode 100644 myFunction/function.json delete mode 100644 myFunction/index.js diff --git a/myExistingFunction/function.json b/myExistingFunction/function.json deleted file mode 100644 index 73b3077d..00000000 --- a/myExistingFunction/function.json +++ /dev/null @@ -1 +0,0 @@ -{"bindings":[{"authLevel":"anonymous","type":"httpTrigger","direction":"in","name":"req","methods":["get","post"]},{"type":"http","direction":"out","name":"res"}]} \ No newline at end of file diff --git a/myExistingFunction/index.js b/myExistingFunction/index.js deleted file mode 100644 index 77df3ac6..00000000 --- a/myExistingFunction/index.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -module.exports.handler = async function (context, req) { - context.log('JavaScript HTTP trigger function processed a request.'); - - if (req.query.name || (req.body && req.body.name)) { - context.res = { - // status: 200, /* Defaults to 200 */ - body: "myExistingFunction " + (req.query.name || req.body.name) - }; - } - else { - context.res = { - status: 400, - body: "Please pass a name on the query string or in the request body" - }; - } -}; \ No newline at end of file diff --git a/myFunction/function.json b/myFunction/function.json deleted file mode 100644 index 73b3077d..00000000 --- a/myFunction/function.json +++ /dev/null @@ -1 +0,0 @@ -{"bindings":[{"authLevel":"anonymous","type":"httpTrigger","direction":"in","name":"req","methods":["get","post"]},{"type":"http","direction":"out","name":"res"}]} \ No newline at end of file diff --git a/myFunction/index.js b/myFunction/index.js deleted file mode 100644 index 8807522b..00000000 --- a/myFunction/index.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -module.exports.handler = async function (context, req) { - context.log('JavaScript HTTP trigger function processed a request.'); - - if (req.query.name || (req.body && req.body.name)) { - context.res = { - // status: 200, /* Defaults to 200 */ - body: "myFunction " + (req.query.name || req.body.name) - }; - } - else { - context.res = { - status: 400, - body: "Please pass a name on the query string or in the request body" - }; - } -}; \ No newline at end of file From d32aef21aeb8fdc622317e1bfc251cb49b9b0df2 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Wed, 29 May 2019 08:38:02 -0700 Subject: [PATCH 10/25] Update message in func plugin --- src/plugins/func/azureFunc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/func/azureFunc.ts b/src/plugins/func/azureFunc.ts index a8af15a3..b1c03f8f 100644 --- a/src/plugins/func/azureFunc.ts +++ b/src/plugins/func/azureFunc.ts @@ -21,6 +21,6 @@ export class AzureFuncPlugin { } private async func() { - this.serverless.cli.log('Got to func'); + this.serverless.cli.log('Use the func plugin to add or remove functions within Function App'); } } \ No newline at end of file From b518cd81ca45635b89022d613106a9a37b3e0ce8 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Wed, 29 May 2019 08:51:07 -0700 Subject: [PATCH 11/25] Removed jest functions and instead added spies --- src/plugins/func/add/azureFuncAdd.test.ts | 11 ++++++----- src/plugins/func/remove/azureFuncRemove.test.ts | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/plugins/func/add/azureFuncAdd.test.ts b/src/plugins/func/add/azureFuncAdd.test.ts index 2c75e2f0..0abd6410 100644 --- a/src/plugins/func/add/azureFuncAdd.test.ts +++ b/src/plugins/func/add/azureFuncAdd.test.ts @@ -7,6 +7,9 @@ import { AzureFuncAddPlugin } from './azureFuncAdd'; describe('Azure Func Add', () => { + const writeFileSpy = jest.spyOn(fs, 'writeFileSync'); + const mkdirSpy = jest.spyOn(fs, 'mkdirSync'); + beforeAll(() => { mock({ 'myExistingFunction': { @@ -15,8 +18,6 @@ describe('Azure Func Add', () => { }, 'serverless.yml': MockFactory.createTestServerlessYml(true) }, {createCwd: true, createTmp: true}) - fs.writeFileSync = jest.fn(); - fs.mkdirSync = jest.fn(); }); afterAll(() => { @@ -47,14 +48,14 @@ describe('Azure Func Add', () => { const functionName = 'myFunction'; options['name'] = functionName; await invokeHook(plugin, 'func:add:add'); - expect(fs.mkdirSync).toBeCalledWith(functionName); - const calls = (fs.writeFileSync as any).mock.calls; + expect(mkdirSpy).toBeCalledWith(functionName); + const calls = (writeFileSpy as any).mock.calls; expect(calls[0][0]).toBe(path.join(functionName, 'index.js')); expect(calls[1][0]).toBe(path.join(functionName, 'function.json')); const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); expectedFunctionsYml[functionName] = MockFactory.createTestFunctionMetadata(functionName); expect(calls[2][0]).toBe('serverless.yml'); - expect(calls[2][1]).toBe(MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) + expect(calls[2][1]).toBe(MockFactory.createTestServerlessYml(true, expectedFunctionsYml)); }); }); \ No newline at end of file diff --git a/src/plugins/func/remove/azureFuncRemove.test.ts b/src/plugins/func/remove/azureFuncRemove.test.ts index b311b8fd..49287b1b 100644 --- a/src/plugins/func/remove/azureFuncRemove.test.ts +++ b/src/plugins/func/remove/azureFuncRemove.test.ts @@ -7,6 +7,9 @@ import { AzureFuncRemovePlugin } from './azureFuncRemove'; describe('Azure Func Add', () => { + const writeFileSpy = jest.spyOn(fs, 'writeFileSync'); + const rimrafSpy = jest.spyOn(rimraf, 'sync'); + beforeAll(() => { mock({ 'function1': { @@ -14,9 +17,7 @@ describe('Azure Func Add', () => { 'function.json': 'contents', }, 'serverless.yml': MockFactory.createTestServerlessYml(true) - }, {createCwd: true, createTmp: true}) - fs.writeFileSync = jest.fn(); - rimraf.sync = jest.fn(); + }, {createCwd: true, createTmp: true}); }); afterAll(() => { @@ -47,9 +48,9 @@ describe('Azure Func Add', () => { const functionName = 'function1'; options['name'] = functionName; await invokeHook(plugin, 'func:remove:remove'); - expect(rimraf.sync).toBeCalledWith(functionName); + expect(rimrafSpy).toBeCalledWith(functionName); const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); delete expectedFunctionsYml[functionName]; - expect(fs.writeFileSync).toBeCalledWith('serverless.yml', MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) + expect(writeFileSpy).toBeCalledWith('serverless.yml', MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) }); }); \ No newline at end of file From acc4fa1fbc1b6c15227d8f98a5e5bb2209064351 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Wed, 29 May 2019 10:41:54 -0700 Subject: [PATCH 12/25] Update to double quotes --- src/index.ts | 26 +++++----- src/plugins/func/add/azureFuncAdd.test.ts | 50 +++++++++--------- src/plugins/func/add/azureFuncAdd.ts | 34 ++++++------ src/plugins/func/azureFunc.ts | 10 ++-- src/plugins/func/funcUtils.test.ts | 24 ++++----- src/plugins/func/funcUtils.ts | 22 ++++---- .../func/remove/azureFuncRemove.test.ts | 46 ++++++++-------- src/plugins/func/remove/azureFuncRemove.ts | 24 ++++----- src/test/mockFactory.ts | 52 +++++++++---------- 9 files changed, 144 insertions(+), 144 deletions(-) diff --git a/src/index.ts b/src/index.ts index 377d6f87..9f5c30bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,19 +4,19 @@ This way only one plugin needs to be added to the service in order to get access whole provider implementation. */ -import Serverless from 'serverless'; -import AzureProvider from './provider/azureProvider'; -import { AzureInvoke } from './plugins/invoke/azureInvoke'; -import { AzureLogs } from './plugins/logs/azureLogs'; -import { AzureRemove } from './plugins/remove/azureRemove'; -import { AzurePackage } from './plugins/package/azurePackage'; -import { AzureDeployPlugin } from './plugins/deploy/azureDeployPlugin'; -import { AzureLoginPlugin } from './plugins/login/loginPlugin'; -import { AzureApimServicePlugin } from './plugins/apim/apimServicePlugin'; -import { AzureApimFunctionPlugin } from './plugins/apim/apimFunctionPlugin'; -import { AzureFuncPlugin } from './plugins/func/azureFunc'; -import { AzureFuncAddPlugin } from './plugins/func/add/azureFuncAdd'; -import { AzureFuncRemovePlugin } from './plugins/func/remove/azureFuncRemove'; +import Serverless from "serverless"; +import AzureProvider from "./provider/azureProvider"; +import { AzureInvoke } from "./plugins/invoke/azureInvoke"; +import { AzureLogs } from "./plugins/logs/azureLogs"; +import { AzureRemove } from "./plugins/remove/azureRemove"; +import { AzurePackage } from "./plugins/package/azurePackage"; +import { AzureDeployPlugin } from "./plugins/deploy/azureDeployPlugin"; +import { AzureLoginPlugin } from "./plugins/login/loginPlugin"; +import { AzureApimServicePlugin } from "./plugins/apim/apimServicePlugin"; +import { AzureApimFunctionPlugin } from "./plugins/apim/apimFunctionPlugin"; +import { AzureFuncPlugin } from "./plugins/func/azureFunc"; +import { AzureFuncAddPlugin } from "./plugins/func/add/azureFuncAdd"; +import { AzureFuncRemovePlugin } from "./plugins/func/remove/azureFuncRemove"; export class AzureIndex { diff --git a/src/plugins/func/add/azureFuncAdd.test.ts b/src/plugins/func/add/azureFuncAdd.test.ts index 0abd6410..b5b62318 100644 --- a/src/plugins/func/add/azureFuncAdd.test.ts +++ b/src/plugins/func/add/azureFuncAdd.test.ts @@ -1,22 +1,22 @@ -import fs from 'fs'; -import mock from 'mock-fs'; -import path from 'path'; -import { MockFactory } from '../../../test/mockFactory'; +import fs from "fs"; +import mock from "mock-fs"; +import path from "path"; +import { MockFactory } from "../../../test/mockFactory"; import { invokeHook } from "../../../test/utils"; -import { AzureFuncAddPlugin } from './azureFuncAdd'; +import { AzureFuncAddPlugin } from "./azureFuncAdd"; -describe('Azure Func Add', () => { +describe("Azure Func Add", () => { - const writeFileSpy = jest.spyOn(fs, 'writeFileSync'); - const mkdirSpy = jest.spyOn(fs, 'mkdirSync'); + const writeFileSpy = jest.spyOn(fs, "writeFileSync"); + const mkdirSpy = jest.spyOn(fs, "mkdirSync"); beforeAll(() => { mock({ - 'myExistingFunction': { - 'index.js': 'contents', - 'function.json': 'contents', + "myExistingFunction": { + "index.js": "contents", + "function.json": "contents", }, - 'serverless.yml': MockFactory.createTestServerlessYml(true) + "serverless.yml": MockFactory.createTestServerlessYml(true) }, {createCwd: true, createTmp: true}) }); @@ -24,38 +24,38 @@ describe('Azure Func Add', () => { mock.restore(); }) - it('returns with missing name', async () => { + it("returns with missing name", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); const plugin = new AzureFuncAddPlugin(sls, options); - await invokeHook(plugin, 'func:add:add'); - expect(sls.cli.log).toBeCalledWith('Need to provide a name of function to add') + await invokeHook(plugin, "func:add:add"); + expect(sls.cli.log).toBeCalledWith("Need to provide a name of function to add") }); - it('returns with pre-existing function', async () => { + it("returns with pre-existing function", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); - options['name'] = 'myExistingFunction'; + options["name"] = "myExistingFunction"; const plugin = new AzureFuncAddPlugin(sls, options); - await invokeHook(plugin, 'func:add:add'); + await invokeHook(plugin, "func:add:add"); expect(sls.cli.log).toBeCalledWith(`Function myExistingFunction already exists`); }); - it('creates function directory and updates serverless.yml', async () => { + it("creates function directory and updates serverless.yml", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); const plugin = new AzureFuncAddPlugin(sls, options); - const functionName = 'myFunction'; - options['name'] = functionName; - await invokeHook(plugin, 'func:add:add'); + const functionName = "myFunction"; + options["name"] = functionName; + await invokeHook(plugin, "func:add:add"); expect(mkdirSpy).toBeCalledWith(functionName); const calls = (writeFileSpy as any).mock.calls; - expect(calls[0][0]).toBe(path.join(functionName, 'index.js')); - expect(calls[1][0]).toBe(path.join(functionName, 'function.json')); + expect(calls[0][0]).toBe(path.join(functionName, "index.js")); + expect(calls[1][0]).toBe(path.join(functionName, "function.json")); const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); expectedFunctionsYml[functionName] = MockFactory.createTestFunctionMetadata(functionName); - expect(calls[2][0]).toBe('serverless.yml'); + expect(calls[2][0]).toBe("serverless.yml"); expect(calls[2][1]).toBe(MockFactory.createTestServerlessYml(true, expectedFunctionsYml)); }); }); \ No newline at end of file diff --git a/src/plugins/func/add/azureFuncAdd.ts b/src/plugins/func/add/azureFuncAdd.ts index c0cf5d7e..58ea4c0a 100644 --- a/src/plugins/func/add/azureFuncAdd.ts +++ b/src/plugins/func/add/azureFuncAdd.ts @@ -1,8 +1,8 @@ -import Serverless from 'serverless'; -import fs from 'fs'; -import yaml from 'js-yaml'; -import path from 'path'; -import { FuncPluginUtils } from '../funcUtils' +import Serverless from "serverless"; +import fs from "fs"; +import yaml from "js-yaml"; +import path from "path"; +import { FuncPluginUtils } from "../funcUtils" export class AzureFuncAddPlugin { @@ -15,14 +15,14 @@ export class AzureFuncAddPlugin { func: { commands: { add: { - usage: 'Add azure function', + usage: "Add azure function", lifecycleEvents: [ - 'add', + "add", ], options: { name: { - usage: 'Name of function to add', - shortcut: 'n', + usage: "Name of function to add", + shortcut: "n", } } } @@ -31,16 +31,16 @@ export class AzureFuncAddPlugin { } this.hooks = { - 'func:add:add': this.add.bind(this) + "func:add:add": this.add.bind(this) }; } private async add() { - if (!('name' in this.options)) { - this.serverless.cli.log('Need to provide a name of function to add'); + if (!("name" in this.options)) { + this.serverless.cli.log("Need to provide a name of function to add"); return; } - const funcToAdd = this.options['name'] + const funcToAdd = this.options["name"] const exists = fs.existsSync(funcToAdd); if (exists) { this.serverless.cli.log(`Function ${funcToAdd} already exists`); @@ -51,14 +51,14 @@ export class AzureFuncAddPlugin { } private createFunctionDir(name: string) { - this.serverless.cli.log('Creating function dir'); + this.serverless.cli.log("Creating function dir"); fs.mkdirSync(name); - fs.writeFileSync(path.join(name, 'index.js'), FuncPluginUtils.getFunctionHandler(name)); - fs.writeFileSync(path.join(name, 'function.json'), FuncPluginUtils.getFunctionJson(name, this.options)) + fs.writeFileSync(path.join(name, "index.js"), FuncPluginUtils.getFunctionHandler(name)); + fs.writeFileSync(path.join(name, "function.json"), FuncPluginUtils.getFunctionJson(name, this.options)) } private addToServerlessYml(name: string) { - this.serverless.cli.log('Adding to serverless.yml'); + this.serverless.cli.log("Adding to serverless.yml"); const functionYml = FuncPluginUtils.getFunctionsYml(); functionYml.functions[name] = FuncPluginUtils.getFunctionSlsObject(name, this.options); FuncPluginUtils.updateFunctionsYml(functionYml); diff --git a/src/plugins/func/azureFunc.ts b/src/plugins/func/azureFunc.ts index b1c03f8f..e6af42f6 100644 --- a/src/plugins/func/azureFunc.ts +++ b/src/plugins/func/azureFunc.ts @@ -1,4 +1,4 @@ -import Serverless from 'serverless'; +import Serverless from "serverless"; export class AzureFuncPlugin { public hooks: { [eventName: string]: Promise }; @@ -7,20 +7,20 @@ export class AzureFuncPlugin { constructor(private serverless: Serverless, private options: Serverless.Options) { this.hooks = { - 'func:func': this.func.bind(this), + "func:func": this.func.bind(this), }; this.commands = { func: { - usage: 'Add or remove functions', + usage: "Add or remove functions", lifecycleEvents: [ - 'func', + "func", ], } } } private async func() { - this.serverless.cli.log('Use the func plugin to add or remove functions within Function App'); + this.serverless.cli.log("Use the func plugin to add or remove functions within Function App"); } } \ No newline at end of file diff --git a/src/plugins/func/funcUtils.test.ts b/src/plugins/func/funcUtils.test.ts index b24eada9..346bbad6 100644 --- a/src/plugins/func/funcUtils.test.ts +++ b/src/plugins/func/funcUtils.test.ts @@ -1,13 +1,13 @@ -import fs from 'fs'; -import mock from 'mock-fs'; -import { MockFactory } from '../../test/mockFactory'; -import { FuncPluginUtils } from './funcUtils'; +import fs from "fs"; +import mock from "mock-fs"; +import { MockFactory } from "../../test/mockFactory"; +import { FuncPluginUtils } from "./funcUtils"; -describe('Func Utils', () => { +describe("Func Utils", () => { beforeAll(() => { mock({ - 'serverless.yml': MockFactory.createTestServerlessYml(true) + "serverless.yml": MockFactory.createTestServerlessYml(true) }, {createCwd: true, createTmp: true}) fs.writeFileSync = jest.fn(); }); @@ -17,28 +17,28 @@ describe('Func Utils', () => { }) - it('gets functions yml', () => { + it("gets functions yml", () => { const funcYaml = FuncPluginUtils.getFunctionsYml(); const expected = { - 'functions': MockFactory.createTestFunctionsMetadata() + "functions": MockFactory.createTestFunctionsMetadata() } expect(funcYaml).toEqual(expected); }); - it('updates functions yml', () => { + it("updates functions yml", () => { FuncPluginUtils.updateFunctionsYml( MockFactory.createTestFunctionsMetadata(3, true), MockFactory.createTestServerlessYml(true, 2) as string); const call = (fs.writeFileSync as any).mock.calls[0] - expect(call[0]).toBe('serverless.yml'); + expect(call[0]).toBe("serverless.yml"); expect(call[1]).toBe(MockFactory.createTestServerlessYml( true, MockFactory.createTestFunctionsMetadata(3) )); }); - it('adds new function name to function handler', () => { - const name = 'This is my function name' + it("adds new function name to function handler", () => { + const name = "This is my function name" expect(FuncPluginUtils.getFunctionHandler(name)) .toContain(`body: "${name} " + (req.query.name || req.body.name)`) }); diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts index ac623c07..133a7ecf 100644 --- a/src/plugins/func/funcUtils.ts +++ b/src/plugins/func/funcUtils.ts @@ -1,12 +1,12 @@ -import yaml from 'js-yaml'; -import fs from 'fs'; +import yaml from "js-yaml"; +import fs from "fs"; const functionsRegex = /functions:([\s\S]*?)\n\n/g export class FuncPluginUtils { public static getServerlessYml() { - return fs.readFileSync('serverless.yml', 'utf-8'); + return fs.readFileSync("serverless.yml", "utf-8"); } public static getFunctionsYml(serverlessYml?: string) { @@ -19,14 +19,14 @@ export class FuncPluginUtils { serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(); const newFunctionsYaml = yaml.dump(functionYml); const newServerlessYaml = serverlessYml.replace(functionsRegex, `${newFunctionsYaml}\n`); - fs.writeFileSync('serverless.yml', newServerlessYaml); + fs.writeFileSync("serverless.yml", newServerlessYaml); } public static getFunctionHandler(name: string) { - return `'use strict'; + return `"use strict"; module.exports.handler = async function (context, req) { - context.log('JavaScript HTTP trigger function processed a request.'); + context.log("JavaScript HTTP trigger function processed a request."); if (req.query.name || (req.body && req.body.name)) { context.res = { @@ -83,15 +83,15 @@ module.exports.handler = async function (context, req) { return [ { http: true, - 'x-azure-settings': { - authLevel: 'anonymous' + "x-azure-settings": { + authLevel: "anonymous" } }, { http: true, - 'x-azure-settings': { - direction: 'out', - name: 'res' + "x-azure-settings": { + direction: "out", + name: "res" } }, ] diff --git a/src/plugins/func/remove/azureFuncRemove.test.ts b/src/plugins/func/remove/azureFuncRemove.test.ts index 49287b1b..6cc11fcb 100644 --- a/src/plugins/func/remove/azureFuncRemove.test.ts +++ b/src/plugins/func/remove/azureFuncRemove.test.ts @@ -1,22 +1,22 @@ -import fs from 'fs'; -import mock from 'mock-fs'; -import rimraf from 'rimraf'; -import { MockFactory } from '../../../test/mockFactory'; +import fs from "fs"; +import mock from "mock-fs"; +import rimraf from "rimraf"; +import { MockFactory } from "../../../test/mockFactory"; import { invokeHook } from "../../../test/utils"; -import { AzureFuncRemovePlugin } from './azureFuncRemove'; +import { AzureFuncRemovePlugin } from "./azureFuncRemove"; -describe('Azure Func Add', () => { +describe("Azure Func Add", () => { - const writeFileSpy = jest.spyOn(fs, 'writeFileSync'); - const rimrafSpy = jest.spyOn(rimraf, 'sync'); + const writeFileSpy = jest.spyOn(fs, "writeFileSync"); + const rimrafSpy = jest.spyOn(rimraf, "sync"); beforeAll(() => { mock({ - 'function1': { - 'index.js': 'contents', - 'function.json': 'contents', + "function1": { + "index.js": "contents", + "function.json": "contents", }, - 'serverless.yml': MockFactory.createTestServerlessYml(true) + "serverless.yml": MockFactory.createTestServerlessYml(true) }, {createCwd: true, createTmp: true}); }); @@ -24,33 +24,33 @@ describe('Azure Func Add', () => { mock.restore(); }) - it('returns with missing name', async () => { + it("returns with missing name", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); const plugin = new AzureFuncRemovePlugin(sls, options); - await invokeHook(plugin, 'func:remove:remove'); - expect(sls.cli.log).toBeCalledWith('Need to provide a name of function to remove') + await invokeHook(plugin, "func:remove:remove"); + expect(sls.cli.log).toBeCalledWith("Need to provide a name of function to remove") }); - it('returns with pre-existing function', async () => { + it("returns with pre-existing function", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); - options['name'] = 'myNonExistingFunction'; + options["name"] = "myNonExistingFunction"; const plugin = new AzureFuncRemovePlugin(sls, options); - await invokeHook(plugin, 'func:remove:remove'); + await invokeHook(plugin, "func:remove:remove"); expect(sls.cli.log).toBeCalledWith(`Function myNonExistingFunction does not exist`); }); - it('deletes directory and updates serverless.yml', async () => { + it("deletes directory and updates serverless.yml", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); const plugin = new AzureFuncRemovePlugin(sls, options); - const functionName = 'function1'; - options['name'] = functionName; - await invokeHook(plugin, 'func:remove:remove'); + const functionName = "function1"; + options["name"] = functionName; + await invokeHook(plugin, "func:remove:remove"); expect(rimrafSpy).toBeCalledWith(functionName); const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); delete expectedFunctionsYml[functionName]; - expect(writeFileSpy).toBeCalledWith('serverless.yml', MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) + expect(writeFileSpy).toBeCalledWith("serverless.yml", MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) }); }); \ No newline at end of file diff --git a/src/plugins/func/remove/azureFuncRemove.ts b/src/plugins/func/remove/azureFuncRemove.ts index c85b9664..c5dd8abf 100644 --- a/src/plugins/func/remove/azureFuncRemove.ts +++ b/src/plugins/func/remove/azureFuncRemove.ts @@ -1,7 +1,7 @@ -import fs from 'fs'; -import rimraf from 'rimraf'; -import Serverless from 'serverless'; -import { FuncPluginUtils } from '../funcUtils'; +import fs from "fs"; +import rimraf from "rimraf"; +import Serverless from "serverless"; +import { FuncPluginUtils } from "../funcUtils"; export class AzureFuncRemovePlugin { public hooks: { [eventName: string]: Promise }; @@ -13,14 +13,14 @@ export class AzureFuncRemovePlugin { func: { commands: { remove: { - usage: 'Remove azure function', + usage: "Remove azure function", lifecycleEvents: [ - 'remove', + "remove", ], options: { name: { - usage: 'Name of function to remove', - shortcut: 'n', + usage: "Name of function to remove", + shortcut: "n", } } } @@ -29,16 +29,16 @@ export class AzureFuncRemovePlugin { } this.hooks = { - 'func:remove:remove': this.remove.bind(this) + "func:remove:remove": this.remove.bind(this) }; } private async remove() { - if (!('name' in this.options)) { - this.serverless.cli.log('Need to provide a name of function to remove'); + if (!("name" in this.options)) { + this.serverless.cli.log("Need to provide a name of function to remove"); return; } - const funcToRemove = this.options['name']; + const funcToRemove = this.options["name"]; const exists = fs.existsSync(funcToRemove); if (!exists) { this.serverless.cli.log(`Function ${funcToRemove} does not exist`); diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index 0fbc87fe..d7af7f30 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -1,9 +1,9 @@ -import { AuthResponse, LinkedSubscription, TokenCredentialsBase } from '@azure/ms-rest-nodeauth'; -import Serverless from 'serverless'; -import Service from 'serverless/classes/Service'; -import Utils = require('serverless/classes/Utils'); -import PluginManager = require('serverless/classes/PluginManager'); -import yaml from 'js-yaml' +import { AuthResponse, LinkedSubscription, TokenCredentialsBase } from "@azure/ms-rest-nodeauth"; +import Serverless from "serverless"; +import Service from "serverless/classes/Service"; +import Utils = require("serverless/classes/Utils"); +import PluginManager = require("serverless/classes/PluginManager"); +import yaml from "js-yaml" export class MockFactory { @@ -30,10 +30,10 @@ export class MockFactory { public static createTestAuthResponse(): AuthResponse { return { - credentials: 'credentials' as any as TokenCredentialsBase, + credentials: "credentials" as any as TokenCredentialsBase, subscriptions: [ { - id: 'azureSubId', + id: "azureSubId", } ] as any as LinkedSubscription[] } @@ -41,16 +41,16 @@ export class MockFactory { public static createTestServerlessYml(asYaml = false, functionMetadata?) { const data = { - 'provider': { - 'name': 'azure', - 'location': 'West US 2' + "provider": { + "name": "azure", + "location": "West US 2" }, - 'plugins': [ - 'serverless-azure-functions' + "plugins": [ + "serverless-azure-functions" ], - 'functions': functionMetadata || MockFactory.createTestFunctionsMetadata(), + "functions": functionMetadata || MockFactory.createTestFunctionsMetadata(), } - return (asYaml) ? yaml.dump(data) + '\n' : data; + return (asYaml) ? yaml.dump(data) + "\n" : data; } public static createTestFunctionsMetadata(functionCount = 2, wrap = false) { @@ -59,24 +59,24 @@ export class MockFactory { const functionName = `function${i+1}`; data[functionName] = MockFactory.createTestFunctionMetadata(functionName) } - return (wrap) ? {'functions': data } : data; + return (wrap) ? {"functions": data } : data; } public static createTestFunctionMetadata(name: string) { return { - 'handler': `${name}/index.handler`, - 'events': [ + "handler": `${name}/index.handler`, + "events": [ { - 'http': true, - 'x-azure-settings': { - 'authLevel': 'anonymous' + "http": true, + "x-azure-settings": { + "authLevel": "anonymous" } }, { - 'http': true, - 'x-azure-settings': { - 'direction': 'out', - 'name': 'res' + "http": true, + "x-azure-settings": { + "direction": "out", + "name": "res" } } ] @@ -85,7 +85,7 @@ export class MockFactory { private static createTestService(): Service { return { - getAllFunctions: jest.fn(() => ['function1']), + getAllFunctions: jest.fn(() => ["function1"]), getFunction: jest.fn(), getAllEventsInFunction: jest.fn(), getAllFunctionsNames: jest.fn(), From c3a5c3389455171569593a0a123b3f3a9026f137 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Wed, 29 May 2019 10:51:07 -0700 Subject: [PATCH 13/25] Remove unused import --- src/plugins/func/add/azureFuncAdd.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/plugins/func/add/azureFuncAdd.ts b/src/plugins/func/add/azureFuncAdd.ts index 58ea4c0a..3316cf25 100644 --- a/src/plugins/func/add/azureFuncAdd.ts +++ b/src/plugins/func/add/azureFuncAdd.ts @@ -1,8 +1,7 @@ -import Serverless from "serverless"; import fs from "fs"; -import yaml from "js-yaml"; import path from "path"; -import { FuncPluginUtils } from "../funcUtils" +import Serverless from "serverless"; +import { FuncPluginUtils } from "../funcUtils"; export class AzureFuncAddPlugin { From c60fc50c9b9fe1305d5b3f9e23ce17e462799c2e Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Wed, 29 May 2019 15:57:59 -0700 Subject: [PATCH 14/25] Update func utils and tests to not use regex --- src/index.ts | 4 - src/plugins/func/add/azureFuncAdd.test.ts | 61 ---------- src/plugins/func/add/azureFuncAdd.ts | 65 ---------- src/plugins/func/azureFunc.test.ts | 113 ++++++++++++++++++ src/plugins/func/azureFunc.ts | 85 ++++++++++++- src/plugins/func/funcUtils.test.ts | 22 ++-- src/plugins/func/funcUtils.ts | 16 +-- .../func/remove/azureFuncRemove.test.ts | 56 --------- src/plugins/func/remove/azureFuncRemove.ts | 57 --------- src/test/mockFactory.ts | 4 +- 10 files changed, 216 insertions(+), 267 deletions(-) delete mode 100644 src/plugins/func/add/azureFuncAdd.test.ts delete mode 100644 src/plugins/func/add/azureFuncAdd.ts create mode 100644 src/plugins/func/azureFunc.test.ts delete mode 100644 src/plugins/func/remove/azureFuncRemove.test.ts delete mode 100644 src/plugins/func/remove/azureFuncRemove.ts diff --git a/src/index.ts b/src/index.ts index 3f48feb0..05e9c210 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,8 +15,6 @@ import { AzureLoginPlugin } from "./plugins/login/loginPlugin"; import { AzureApimServicePlugin } from "./plugins/apim/apimServicePlugin"; import { AzureApimFunctionPlugin } from "./plugins/apim/apimFunctionPlugin"; import { AzureFuncPlugin } from "./plugins/func/azureFunc"; -import { AzureFuncAddPlugin } from "./plugins/func/add/azureFuncAdd"; -import { AzureFuncRemovePlugin } from "./plugins/func/remove/azureFuncRemove"; export class AzureIndex { @@ -34,8 +32,6 @@ export class AzureIndex { this.serverless.pluginManager.addPlugin(AzureApimServicePlugin); this.serverless.pluginManager.addPlugin(AzureApimFunctionPlugin); this.serverless.pluginManager.addPlugin(AzureFuncPlugin); - this.serverless.pluginManager.addPlugin(AzureFuncAddPlugin); - this.serverless.pluginManager.addPlugin(AzureFuncRemovePlugin); } } diff --git a/src/plugins/func/add/azureFuncAdd.test.ts b/src/plugins/func/add/azureFuncAdd.test.ts deleted file mode 100644 index b5b62318..00000000 --- a/src/plugins/func/add/azureFuncAdd.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import fs from "fs"; -import mock from "mock-fs"; -import path from "path"; -import { MockFactory } from "../../../test/mockFactory"; -import { invokeHook } from "../../../test/utils"; -import { AzureFuncAddPlugin } from "./azureFuncAdd"; - -describe("Azure Func Add", () => { - - const writeFileSpy = jest.spyOn(fs, "writeFileSync"); - const mkdirSpy = jest.spyOn(fs, "mkdirSync"); - - beforeAll(() => { - mock({ - "myExistingFunction": { - "index.js": "contents", - "function.json": "contents", - }, - "serverless.yml": MockFactory.createTestServerlessYml(true) - }, {createCwd: true, createTmp: true}) - }); - - afterAll(() => { - mock.restore(); - }) - - it("returns with missing name", async () => { - const sls = MockFactory.createTestServerless(); - const options = MockFactory.createTestServerlessOptions(); - const plugin = new AzureFuncAddPlugin(sls, options); - await invokeHook(plugin, "func:add:add"); - expect(sls.cli.log).toBeCalledWith("Need to provide a name of function to add") - }); - - it("returns with pre-existing function", async () => { - const sls = MockFactory.createTestServerless(); - const options = MockFactory.createTestServerlessOptions(); - options["name"] = "myExistingFunction"; - const plugin = new AzureFuncAddPlugin(sls, options); - await invokeHook(plugin, "func:add:add"); - expect(sls.cli.log).toBeCalledWith(`Function myExistingFunction already exists`); - }); - - it("creates function directory and updates serverless.yml", async () => { - const sls = MockFactory.createTestServerless(); - const options = MockFactory.createTestServerlessOptions(); - const plugin = new AzureFuncAddPlugin(sls, options); - const functionName = "myFunction"; - options["name"] = functionName; - await invokeHook(plugin, "func:add:add"); - expect(mkdirSpy).toBeCalledWith(functionName); - const calls = (writeFileSpy as any).mock.calls; - expect(calls[0][0]).toBe(path.join(functionName, "index.js")); - expect(calls[1][0]).toBe(path.join(functionName, "function.json")); - - const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); - expectedFunctionsYml[functionName] = MockFactory.createTestFunctionMetadata(functionName); - expect(calls[2][0]).toBe("serverless.yml"); - expect(calls[2][1]).toBe(MockFactory.createTestServerlessYml(true, expectedFunctionsYml)); - }); -}); \ No newline at end of file diff --git a/src/plugins/func/add/azureFuncAdd.ts b/src/plugins/func/add/azureFuncAdd.ts deleted file mode 100644 index 3316cf25..00000000 --- a/src/plugins/func/add/azureFuncAdd.ts +++ /dev/null @@ -1,65 +0,0 @@ -import fs from "fs"; -import path from "path"; -import Serverless from "serverless"; -import { FuncPluginUtils } from "../funcUtils"; - - -export class AzureFuncAddPlugin { - public hooks: { [eventName: string]: Promise }; - public commands: any; - - constructor(private serverless: Serverless, private options: Serverless.Options) { - - this.commands = { - func: { - commands: { - add: { - usage: "Add azure function", - lifecycleEvents: [ - "add", - ], - options: { - name: { - usage: "Name of function to add", - shortcut: "n", - } - } - } - } - } - } - - this.hooks = { - "func:add:add": this.add.bind(this) - }; - } - - private async add() { - if (!("name" in this.options)) { - this.serverless.cli.log("Need to provide a name of function to add"); - return; - } - const funcToAdd = this.options["name"] - const exists = fs.existsSync(funcToAdd); - if (exists) { - this.serverless.cli.log(`Function ${funcToAdd} already exists`); - return; - } - this.createFunctionDir(funcToAdd); - this.addToServerlessYml(funcToAdd); - } - - private createFunctionDir(name: string) { - this.serverless.cli.log("Creating function dir"); - fs.mkdirSync(name); - fs.writeFileSync(path.join(name, "index.js"), FuncPluginUtils.getFunctionHandler(name)); - fs.writeFileSync(path.join(name, "function.json"), FuncPluginUtils.getFunctionJson(name, this.options)) - } - - private addToServerlessYml(name: string) { - this.serverless.cli.log("Adding to serverless.yml"); - const functionYml = FuncPluginUtils.getFunctionsYml(); - functionYml.functions[name] = FuncPluginUtils.getFunctionSlsObject(name, this.options); - FuncPluginUtils.updateFunctionsYml(functionYml); - } -} \ No newline at end of file diff --git a/src/plugins/func/azureFunc.test.ts b/src/plugins/func/azureFunc.test.ts new file mode 100644 index 00000000..ef35a516 --- /dev/null +++ b/src/plugins/func/azureFunc.test.ts @@ -0,0 +1,113 @@ +import fs from "fs"; +import mock from "mock-fs"; +import path from "path"; +import { MockFactory } from "../../test/mockFactory"; +import { invokeHook } from "../../test/utils"; +import { AzureFuncPlugin } from "./azureFunc"; +import rimraf from "rimraf"; + +describe("Azure Func Plugin", () => { + + beforeAll(() => { + mock({ + "myExistingFunction": { + "index.js": "contents", + "function.json": "contents", + }, + "serverless.yml": MockFactory.createTestServerlessYml(true) + }, {createCwd: true, createTmp: true}) + }); + + afterAll(() => { + mock.restore(); + }); + + describe("Add command", () => { + const writeFileSpy = jest.spyOn(fs, "writeFileSync"); + const mkdirSpy = jest.spyOn(fs, "mkdirSync"); + + it("returns with missing name", async () => { + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + const plugin = new AzureFuncPlugin(sls, options); + await invokeHook(plugin, "func:add:add"); + expect(sls.cli.log).toBeCalledWith("Need to provide a name of function to add") + }); + + it("returns with pre-existing function", async () => { + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + options["name"] = "myExistingFunction"; + const plugin = new AzureFuncPlugin(sls, options); + await invokeHook(plugin, "func:add:add"); + expect(sls.cli.log).toBeCalledWith(`Function myExistingFunction already exists`); + }); + + it("creates function directory and updates serverless.yml", async () => { + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + const plugin = new AzureFuncPlugin(sls, options); + const functionName = "myFunction"; + options["name"] = functionName; + await invokeHook(plugin, "func:add:add"); + expect(mkdirSpy).toBeCalledWith(functionName); + const calls = (writeFileSpy as any).mock.calls; + expect(calls[0][0]).toBe(path.join(functionName, "index.js")); + expect(calls[1][0]).toBe(path.join(functionName, "function.json")); + + const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); + expectedFunctionsYml[functionName] = MockFactory.createTestFunctionMetadata(functionName); + expect(calls[2][0]).toBe("serverless.yml"); + expect(calls[2][1]).toBe(MockFactory.createTestServerlessYml(true, expectedFunctionsYml)); + }); + }); + + describe("Remove command", () => { + const writeFileSpy = jest.spyOn(fs, "writeFileSync"); + const rimrafSpy = jest.spyOn(rimraf, "sync"); + + beforeAll(() => { + mock({ + "function1": { + "index.js": "contents", + "function.json": "contents", + }, + "serverless.yml": MockFactory.createTestServerlessYml(true) + }, {createCwd: true, createTmp: true}); + }); + + afterAll(() => { + mock.restore(); + }) + + it("returns with missing name", async () => { + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + const plugin = new AzureFuncPlugin(sls, options); + await invokeHook(plugin, "func:remove:remove"); + expect(sls.cli.log).toBeCalledWith("Need to provide a name of function to remove") + }); + + it("returns with pre-existing function", async () => { + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + options["name"] = "myNonExistingFunction"; + const plugin = new AzureFuncPlugin(sls, options); + await invokeHook(plugin, "func:remove:remove"); + expect(sls.cli.log).toBeCalledWith(`Function myNonExistingFunction does not exist`); + }); + + it("deletes directory and updates serverless.yml", async () => { + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + const plugin = new AzureFuncPlugin(sls, options); + const functionName = "function1"; + options["name"] = functionName; + await invokeHook(plugin, "func:remove:remove"); + expect(rimrafSpy).toBeCalledWith(functionName); + const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); + delete expectedFunctionsYml[functionName]; + expect(writeFileSpy).toBeCalledWith("serverless.yml", MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) + }); + }); +}); \ No newline at end of file diff --git a/src/plugins/func/azureFunc.ts b/src/plugins/func/azureFunc.ts index e6af42f6..b4f9b612 100644 --- a/src/plugins/func/azureFunc.ts +++ b/src/plugins/func/azureFunc.ts @@ -1,13 +1,19 @@ +import fs from "fs"; +import path from "path"; +import rimraf from "rimraf"; import Serverless from "serverless"; +import { FuncPluginUtils } from "./funcUtils"; export class AzureFuncPlugin { public hooks: { [eventName: string]: Promise }; public commands: any; - constructor(private serverless: Serverless, private options: Serverless.Options) { + public constructor(private serverless: Serverless, private options: Serverless.Options) { this.hooks = { "func:func": this.func.bind(this), + "func:add:add": this.add.bind(this), + "func:remove:remove": this.remove.bind(this) }; this.commands = { @@ -16,6 +22,32 @@ export class AzureFuncPlugin { lifecycleEvents: [ "func", ], + commands: { + add: { + usage: "Add azure function", + lifecycleEvents: [ + "add", + ], + options: { + name: { + usage: "Name of function to add", + shortcut: "n", + } + } + }, + remove: { + usage: "Remove azure function", + lifecycleEvents: [ + "remove", + ], + options: { + name: { + usage: "Name of function to remove", + shortcut: "n", + } + } + } + } } } } @@ -23,4 +55,55 @@ export class AzureFuncPlugin { private async func() { this.serverless.cli.log("Use the func plugin to add or remove functions within Function App"); } + + private async add() { + if (!("name" in this.options)) { + this.serverless.cli.log("Need to provide a name of function to add"); + return; + } + const funcToAdd = this.options["name"] + const exists = fs.existsSync(funcToAdd); + if (exists) { + this.serverless.cli.log(`Function ${funcToAdd} already exists`); + return; + } + this.createFunctionDir(funcToAdd); + this.addToServerlessYml(funcToAdd); + } + + private createFunctionDir(name: string) { + this.serverless.cli.log("Creating function dir"); + fs.mkdirSync(name); + fs.writeFileSync(path.join(name, "index.js"), FuncPluginUtils.getFunctionHandler(name)); + fs.writeFileSync(path.join(name, "function.json"), FuncPluginUtils.getFunctionJson(name, this.options)) + } + + private addToServerlessYml(name: string) { + this.serverless.cli.log("Adding to serverless.yml"); + const functionYml = FuncPluginUtils.getFunctionsYml(); + functionYml.functions[name] = FuncPluginUtils.getFunctionSlsObject(name, this.options); + FuncPluginUtils.updateFunctionsYml(functionYml); + } + + private async remove() { + if (!("name" in this.options)) { + this.serverless.cli.log("Need to provide a name of function to remove"); + return; + } + const funcToRemove = this.options["name"]; + const exists = fs.existsSync(funcToRemove); + if (!exists) { + this.serverless.cli.log(`Function ${funcToRemove} does not exist`); + return; + } + this.serverless.cli.log(`Removing ${funcToRemove}`); + rimraf.sync(funcToRemove); + await this.removeFromServerlessYml(funcToRemove); + } + + private async removeFromServerlessYml(name: string) { + const functionYml = FuncPluginUtils.getFunctionsYml(); + delete functionYml.functions[name]; + FuncPluginUtils.updateFunctionsYml(functionYml) + } } \ No newline at end of file diff --git a/src/plugins/func/funcUtils.test.ts b/src/plugins/func/funcUtils.test.ts index 346bbad6..fb467044 100644 --- a/src/plugins/func/funcUtils.test.ts +++ b/src/plugins/func/funcUtils.test.ts @@ -5,11 +5,12 @@ import { FuncPluginUtils } from "./funcUtils"; describe("Func Utils", () => { + const writeFileSync = jest.spyOn(fs, "writeFileSync"); + beforeAll(() => { mock({ "serverless.yml": MockFactory.createTestServerlessYml(true) }, {createCwd: true, createTmp: true}) - fs.writeFileSync = jest.fn(); }); afterAll(() => { @@ -19,22 +20,21 @@ describe("Func Utils", () => { it("gets functions yml", () => { const funcYaml = FuncPluginUtils.getFunctionsYml(); - const expected = { - "functions": MockFactory.createTestFunctionsMetadata() - } - expect(funcYaml).toEqual(expected); + expect(funcYaml).toEqual(MockFactory.createTestFunctionsMetadata()); }); it("updates functions yml", () => { - FuncPluginUtils.updateFunctionsYml( - MockFactory.createTestFunctionsMetadata(3, true), - MockFactory.createTestServerlessYml(true, 2) as string); - const call = (fs.writeFileSync as any).mock.calls[0] + const updatedFunctions = MockFactory.createTestFunctionsMetadata(3); + const originalSls = MockFactory.createTestServerlessYml(false, 2); + + FuncPluginUtils.updateFunctionsYml(updatedFunctions, originalSls); + const call = writeFileSync.mock.calls[0] expect(call[0]).toBe("serverless.yml"); - expect(call[1]).toBe(MockFactory.createTestServerlessYml( + const expected = MockFactory.createTestServerlessYml( true, MockFactory.createTestFunctionsMetadata(3) - )); + ); + expect(call[1]).toBe(expected); }); it("adds new function name to function handler", () => { diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts index 133a7ecf..713af132 100644 --- a/src/plugins/func/funcUtils.ts +++ b/src/plugins/func/funcUtils.ts @@ -1,25 +1,21 @@ import yaml from "js-yaml"; import fs from "fs"; -const functionsRegex = /functions:([\s\S]*?)\n\n/g - export class FuncPluginUtils { public static getServerlessYml() { - return fs.readFileSync("serverless.yml", "utf-8"); + return yaml.safeLoad(fs.readFileSync("serverless.yml", "utf-8")); } - public static getFunctionsYml(serverlessYml?: string) { + public static getFunctionsYml(serverlessYml?: any) { serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(); - const functionsSection = serverlessYml.match(functionsRegex)[0]; - return yaml.safeLoad(functionsSection); + return serverlessYml["functions"]; } - public static updateFunctionsYml(functionYml: any, serverlessYml?: string) { + public static updateFunctionsYml(functionYml: any, serverlessYml?: any) { serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(); - const newFunctionsYaml = yaml.dump(functionYml); - const newServerlessYaml = serverlessYml.replace(functionsRegex, `${newFunctionsYaml}\n`); - fs.writeFileSync("serverless.yml", newServerlessYaml); + serverlessYml["functions"] = functionYml; + fs.writeFileSync("serverless.yml", yaml.dump(serverlessYml)); } public static getFunctionHandler(name: string) { diff --git a/src/plugins/func/remove/azureFuncRemove.test.ts b/src/plugins/func/remove/azureFuncRemove.test.ts deleted file mode 100644 index 6cc11fcb..00000000 --- a/src/plugins/func/remove/azureFuncRemove.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import fs from "fs"; -import mock from "mock-fs"; -import rimraf from "rimraf"; -import { MockFactory } from "../../../test/mockFactory"; -import { invokeHook } from "../../../test/utils"; -import { AzureFuncRemovePlugin } from "./azureFuncRemove"; - -describe("Azure Func Add", () => { - - const writeFileSpy = jest.spyOn(fs, "writeFileSync"); - const rimrafSpy = jest.spyOn(rimraf, "sync"); - - beforeAll(() => { - mock({ - "function1": { - "index.js": "contents", - "function.json": "contents", - }, - "serverless.yml": MockFactory.createTestServerlessYml(true) - }, {createCwd: true, createTmp: true}); - }); - - afterAll(() => { - mock.restore(); - }) - - it("returns with missing name", async () => { - const sls = MockFactory.createTestServerless(); - const options = MockFactory.createTestServerlessOptions(); - const plugin = new AzureFuncRemovePlugin(sls, options); - await invokeHook(plugin, "func:remove:remove"); - expect(sls.cli.log).toBeCalledWith("Need to provide a name of function to remove") - }); - - it("returns with pre-existing function", async () => { - const sls = MockFactory.createTestServerless(); - const options = MockFactory.createTestServerlessOptions(); - options["name"] = "myNonExistingFunction"; - const plugin = new AzureFuncRemovePlugin(sls, options); - await invokeHook(plugin, "func:remove:remove"); - expect(sls.cli.log).toBeCalledWith(`Function myNonExistingFunction does not exist`); - }); - - it("deletes directory and updates serverless.yml", async () => { - const sls = MockFactory.createTestServerless(); - const options = MockFactory.createTestServerlessOptions(); - const plugin = new AzureFuncRemovePlugin(sls, options); - const functionName = "function1"; - options["name"] = functionName; - await invokeHook(plugin, "func:remove:remove"); - expect(rimrafSpy).toBeCalledWith(functionName); - const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); - delete expectedFunctionsYml[functionName]; - expect(writeFileSpy).toBeCalledWith("serverless.yml", MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) - }); -}); \ No newline at end of file diff --git a/src/plugins/func/remove/azureFuncRemove.ts b/src/plugins/func/remove/azureFuncRemove.ts deleted file mode 100644 index c5dd8abf..00000000 --- a/src/plugins/func/remove/azureFuncRemove.ts +++ /dev/null @@ -1,57 +0,0 @@ -import fs from "fs"; -import rimraf from "rimraf"; -import Serverless from "serverless"; -import { FuncPluginUtils } from "../funcUtils"; - -export class AzureFuncRemovePlugin { - public hooks: { [eventName: string]: Promise }; - public commands: any; - - constructor(private serverless: Serverless, private options: Serverless.Options) { - - this.commands = { - func: { - commands: { - remove: { - usage: "Remove azure function", - lifecycleEvents: [ - "remove", - ], - options: { - name: { - usage: "Name of function to remove", - shortcut: "n", - } - } - } - } - } - } - - this.hooks = { - "func:remove:remove": this.remove.bind(this) - }; - } - - private async remove() { - if (!("name" in this.options)) { - this.serverless.cli.log("Need to provide a name of function to remove"); - return; - } - const funcToRemove = this.options["name"]; - const exists = fs.existsSync(funcToRemove); - if (!exists) { - this.serverless.cli.log(`Function ${funcToRemove} does not exist`); - return; - } - this.serverless.cli.log(`Removing ${funcToRemove}`); - rimraf.sync(funcToRemove); - await this.removeFromServerlessYml(funcToRemove); - } - - private async removeFromServerlessYml(name: string) { - const functionYml = FuncPluginUtils.getFunctionsYml(); - delete functionYml.functions[name]; - FuncPluginUtils.updateFunctionsYml(functionYml) - } -} \ No newline at end of file diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index d7af7f30..0dca6430 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -48,9 +48,9 @@ export class MockFactory { "plugins": [ "serverless-azure-functions" ], - "functions": functionMetadata || MockFactory.createTestFunctionsMetadata(), + "functions": functionMetadata || MockFactory.createTestFunctionsMetadata(2, false), } - return (asYaml) ? yaml.dump(data) + "\n" : data; + return (asYaml) ? yaml.dump(data) : data; } public static createTestFunctionsMetadata(functionCount = 2, wrap = false) { From 490e98321592d45b455428253b4fcbd8ea71bc4f Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Wed, 29 May 2019 16:17:31 -0700 Subject: [PATCH 15/25] Fix bug in altering serverless yml --- src/plugins/func/azureFunc.test.ts | 12 ++++++++++-- src/plugins/func/azureFunc.ts | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/plugins/func/azureFunc.test.ts b/src/plugins/func/azureFunc.test.ts index ef35a516..f1f34061 100644 --- a/src/plugins/func/azureFunc.test.ts +++ b/src/plugins/func/azureFunc.test.ts @@ -22,6 +22,14 @@ describe("Azure Func Plugin", () => { mock.restore(); }); + it("displays a help message", async () => { + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + const plugin = new AzureFuncPlugin(sls, options); + await invokeHook(plugin, "func:func"); + expect(sls.cli.log).toBeCalledWith("Use the func plugin to add or remove functions within Function App"); + }) + describe("Add command", () => { const writeFileSpy = jest.spyOn(fs, "writeFileSync"); const mkdirSpy = jest.spyOn(fs, "mkdirSync"); @@ -46,9 +54,9 @@ describe("Azure Func Plugin", () => { it("creates function directory and updates serverless.yml", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); - const plugin = new AzureFuncPlugin(sls, options); const functionName = "myFunction"; options["name"] = functionName; + const plugin = new AzureFuncPlugin(sls, options); await invokeHook(plugin, "func:add:add"); expect(mkdirSpy).toBeCalledWith(functionName); const calls = (writeFileSpy as any).mock.calls; @@ -78,7 +86,7 @@ describe("Azure Func Plugin", () => { afterAll(() => { mock.restore(); - }) + }); it("returns with missing name", async () => { const sls = MockFactory.createTestServerless(); diff --git a/src/plugins/func/azureFunc.ts b/src/plugins/func/azureFunc.ts index b4f9b612..8474ee4a 100644 --- a/src/plugins/func/azureFunc.ts +++ b/src/plugins/func/azureFunc.ts @@ -81,7 +81,7 @@ export class AzureFuncPlugin { private addToServerlessYml(name: string) { this.serverless.cli.log("Adding to serverless.yml"); const functionYml = FuncPluginUtils.getFunctionsYml(); - functionYml.functions[name] = FuncPluginUtils.getFunctionSlsObject(name, this.options); + functionYml[name] = FuncPluginUtils.getFunctionSlsObject(name, this.options); FuncPluginUtils.updateFunctionsYml(functionYml); } @@ -103,7 +103,7 @@ export class AzureFuncPlugin { private async removeFromServerlessYml(name: string) { const functionYml = FuncPluginUtils.getFunctionsYml(); - delete functionYml.functions[name]; + delete functionYml[name]; FuncPluginUtils.updateFunctionsYml(functionYml) } } \ No newline at end of file From ef2ee1bb6acc52b9d40be64abb7553fe277d8c63 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Thu, 30 May 2019 06:34:13 -0700 Subject: [PATCH 16/25] Clearing all mocks after each test to avoid conflicts --- src/plugins/func/azureFunc.test.ts | 53 +++++++++++++++++------------- src/plugins/func/funcUtils.test.ts | 16 +++++---- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/plugins/func/azureFunc.test.ts b/src/plugins/func/azureFunc.test.ts index f1f34061..1da8935b 100644 --- a/src/plugins/func/azureFunc.test.ts +++ b/src/plugins/func/azureFunc.test.ts @@ -1,5 +1,5 @@ import fs from "fs"; -import mock from "mock-fs"; +import mockFs from "mock-fs"; import path from "path"; import { MockFactory } from "../../test/mockFactory"; import { invokeHook } from "../../test/utils"; @@ -8,20 +8,6 @@ import rimraf from "rimraf"; describe("Azure Func Plugin", () => { - beforeAll(() => { - mock({ - "myExistingFunction": { - "index.js": "contents", - "function.json": "contents", - }, - "serverless.yml": MockFactory.createTestServerlessYml(true) - }, {createCwd: true, createTmp: true}) - }); - - afterAll(() => { - mock.restore(); - }); - it("displays a help message", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); @@ -31,8 +17,24 @@ describe("Azure Func Plugin", () => { }) describe("Add command", () => { - const writeFileSpy = jest.spyOn(fs, "writeFileSync"); - const mkdirSpy = jest.spyOn(fs, "mkdirSync"); + + beforeAll(() => { + mockFs({ + "myExistingFunction": { + "index.js": "contents", + "function.json": "contents", + }, + "serverless.yml": MockFactory.createTestServerlessYml(true) + }, {createCwd: true, createTmp: true}) + }); + + afterAll(() => { + mockFs.restore(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); it("returns with missing name", async () => { const sls = MockFactory.createTestServerless(); @@ -57,6 +59,8 @@ describe("Azure Func Plugin", () => { const functionName = "myFunction"; options["name"] = functionName; const plugin = new AzureFuncPlugin(sls, options); + const writeFileSpy = jest.spyOn(fs, "writeFileSync"); + const mkdirSpy = jest.spyOn(fs, "mkdirSync"); await invokeHook(plugin, "func:add:add"); expect(mkdirSpy).toBeCalledWith(functionName); const calls = (writeFileSpy as any).mock.calls; @@ -70,12 +74,9 @@ describe("Azure Func Plugin", () => { }); }); - describe("Remove command", () => { - const writeFileSpy = jest.spyOn(fs, "writeFileSync"); - const rimrafSpy = jest.spyOn(rimraf, "sync"); - + describe("Remove command", () => { beforeAll(() => { - mock({ + mockFs({ "function1": { "index.js": "contents", "function.json": "contents", @@ -83,9 +84,13 @@ describe("Azure Func Plugin", () => { "serverless.yml": MockFactory.createTestServerlessYml(true) }, {createCwd: true, createTmp: true}); }); + + afterEach(() => { + jest.clearAllMocks(); + }); afterAll(() => { - mock.restore(); + mockFs.restore(); }); it("returns with missing name", async () => { @@ -111,6 +116,8 @@ describe("Azure Func Plugin", () => { const plugin = new AzureFuncPlugin(sls, options); const functionName = "function1"; options["name"] = functionName; + const writeFileSpy = jest.spyOn(fs, "writeFileSync"); + const rimrafSpy = jest.spyOn(rimraf, "sync"); await invokeHook(plugin, "func:remove:remove"); expect(rimrafSpy).toBeCalledWith(functionName); const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); diff --git a/src/plugins/func/funcUtils.test.ts b/src/plugins/func/funcUtils.test.ts index fb467044..e39f1226 100644 --- a/src/plugins/func/funcUtils.test.ts +++ b/src/plugins/func/funcUtils.test.ts @@ -1,21 +1,23 @@ import fs from "fs"; -import mock from "mock-fs"; +import mockFs from "mock-fs"; import { MockFactory } from "../../test/mockFactory"; import { FuncPluginUtils } from "./funcUtils"; describe("Func Utils", () => { - const writeFileSync = jest.spyOn(fs, "writeFileSync"); - beforeAll(() => { - mock({ + mockFs({ "serverless.yml": MockFactory.createTestServerlessYml(true) }, {createCwd: true, createTmp: true}) }); + afterEach(() => { + jest.clearAllMocks(); + }); + afterAll(() => { - mock.restore(); - }) + mockFs.restore(); + }); it("gets functions yml", () => { @@ -26,7 +28,7 @@ describe("Func Utils", () => { it("updates functions yml", () => { const updatedFunctions = MockFactory.createTestFunctionsMetadata(3); const originalSls = MockFactory.createTestServerlessYml(false, 2); - + const writeFileSync = jest.spyOn(fs, "writeFileSync"); FuncPluginUtils.updateFunctionsYml(updatedFunctions, originalSls); const call = writeFileSync.mock.calls[0] expect(call[0]).toBe("serverless.yml"); From 4f59c32831c74728e95994a6e8c554820835fc4a Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Thu, 30 May 2019 07:28:22 -0700 Subject: [PATCH 17/25] Added template file for generated javascript --- serverless.yml | 36 ++++++++++++++++++++++++++++++ src/plugins/func/azureFunc.test.ts | 9 ++++---- src/plugins/func/funcHandler.txt | 18 +++++++++++++++ src/plugins/func/funcUtils.test.ts | 9 +++++--- src/plugins/func/funcUtils.ts | 27 ++++++---------------- src/shared/utils.ts | 8 +++++++ src/test/mockFactory.ts | 20 +++++++++++++++++ 7 files changed, 100 insertions(+), 27 deletions(-) create mode 100644 serverless.yml create mode 100644 src/plugins/func/funcHandler.txt diff --git a/serverless.yml b/serverless.yml new file mode 100644 index 00000000..3d4e61a2 --- /dev/null +++ b/serverless.yml @@ -0,0 +1,36 @@ +provider: + name: azure + location: West US 2 +plugins: + - serverless-azure-functions +functions: + function1: + handler: function1/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res + function2: + handler: function2/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res + function3: + handler: function3/index.handler + events: + - http: true + x-azure-settings: + authLevel: anonymous + - http: true + x-azure-settings: + direction: out + name: res diff --git a/src/plugins/func/azureFunc.test.ts b/src/plugins/func/azureFunc.test.ts index 1da8935b..2d576a5b 100644 --- a/src/plugins/func/azureFunc.test.ts +++ b/src/plugins/func/azureFunc.test.ts @@ -75,6 +75,7 @@ describe("Azure Func Plugin", () => { }); describe("Remove command", () => { + beforeAll(() => { mockFs({ "function1": { @@ -84,14 +85,14 @@ describe("Azure Func Plugin", () => { "serverless.yml": MockFactory.createTestServerlessYml(true) }, {createCwd: true, createTmp: true}); }); - - afterEach(() => { - jest.clearAllMocks(); - }); afterAll(() => { mockFs.restore(); }); + + afterEach(() => { + jest.clearAllMocks(); + }); it("returns with missing name", async () => { const sls = MockFactory.createTestServerless(); diff --git a/src/plugins/func/funcHandler.txt b/src/plugins/func/funcHandler.txt new file mode 100644 index 00000000..a9154c1c --- /dev/null +++ b/src/plugins/func/funcHandler.txt @@ -0,0 +1,18 @@ +"use strict"; + +module.exports.handler = async function (context, req) { + context.log("JavaScript HTTP trigger function processed a request."); + + if (req.query.name || (req.body && req.body.name)) { + context.res = { + // status: 200, /* Defaults to 200 */ + body: "${name} " + (req.query.name || req.body.name) + }; + } + else { + context.res = { + status: 400, + body: "Please pass a name on the query string or in the request body" + }; + } +}; \ No newline at end of file diff --git a/src/plugins/func/funcUtils.test.ts b/src/plugins/func/funcUtils.test.ts index e39f1226..ca6f62d2 100644 --- a/src/plugins/func/funcUtils.test.ts +++ b/src/plugins/func/funcUtils.test.ts @@ -5,10 +5,13 @@ import { FuncPluginUtils } from "./funcUtils"; describe("Func Utils", () => { + + beforeAll(() => { mockFs({ - "serverless.yml": MockFactory.createTestServerlessYml(true) - }, {createCwd: true, createTmp: true}) + "serverless.yml": MockFactory.createTestServerlessYml(true), + "src/plugins/func/funcHandler.txt": MockFactory.createTestHandler(), + }, {createCwd: false, createTmp: true}) }); afterEach(() => { @@ -42,6 +45,6 @@ describe("Func Utils", () => { it("adds new function name to function handler", () => { const name = "This is my function name" expect(FuncPluginUtils.getFunctionHandler(name)) - .toContain(`body: "${name} " + (req.query.name || req.body.name)`) + .toContain(`body: '${name} ' + (req.query.name || req.body.name)`) }); }); \ No newline at end of file diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts index 713af132..52c1fa5a 100644 --- a/src/plugins/func/funcUtils.ts +++ b/src/plugins/func/funcUtils.ts @@ -1,5 +1,7 @@ -import yaml from "js-yaml"; import fs from "fs"; +import yaml from "js-yaml"; +import path from "path"; +import { Utils } from "../../shared/utils"; export class FuncPluginUtils { @@ -19,24 +21,10 @@ export class FuncPluginUtils { } public static getFunctionHandler(name: string) { - return `"use strict"; - -module.exports.handler = async function (context, req) { - context.log("JavaScript HTTP trigger function processed a request."); - - if (req.query.name || (req.body && req.body.name)) { - context.res = { - // status: 200, /* Defaults to 200 */ - body: "${name} " + (req.query.name || req.body.name) - }; - } - else { - context.res = { - status: 400, - body: "Please pass a name on the query string or in the request body" - }; - } -};`; + const filePath = path.resolve(process.cwd(), "src", "plugins", "func", "funcHandler.txt") + return Utils.interpolateFile(filePath, new Map([ + ["name", name] + ])); } public static getFunctionJson(name: string, options: any) { @@ -64,7 +52,6 @@ module.exports.handler = async function (context, req) { } public static getFunctionSlsObject(name: string, options: any) { - return FuncPluginUtils.defaultFunctionSlsObject(name); } diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 96ab9f06..65d58b36 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -1,5 +1,6 @@ import { BindingUtils } from "./bindings"; import { constants } from "./constants"; +import fs from 'fs'; export interface FunctionMetadata { entryPoint: any; @@ -110,4 +111,11 @@ export class Utils { return metaData; } + + public static interpolateFile(path: string, params: Map) { + const template = fs.readFileSync(path, "utf8"); + const names = params.keys(); + const vals = params.values(); + return new Function(...names, `return \`${template}\`;`)(...vals); + } } diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index 0dca6430..83e77d34 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -62,6 +62,26 @@ export class MockFactory { return (wrap) ? {"functions": data } : data; } + public static createTestHandler() { + // The mock-fs module doesn't allow for template/multi-line strings... + return "'use strict'\n" + + "module.exports.handler = async function (context, req) {\n" + + "\tcontext.log('JavaScript HTTP trigger function processed a request.');\n" + + "\tif (req.query.name || (req.body && req.body.name)) {\n" + + "\t\tcontext.res = {\n" + + "\t\t\t// status: 200, /* Defaults to 200 */\n" + + "\t\t\tbody: '${name} ' + (req.query.name || req.body.name)\n" + + "\t\t};\n" + + "\t}\n" + + "\telse {\n" + + "\t\tcontext.res = {\n" + + "\t\t\tstatus: 400,\n" + + "\t\t\tbody: 'Please pass a name on the query string or in the request body'\n" + + "\t\t};\n" + + "\t}\n" + + "};\n" + } + public static createTestFunctionMetadata(name: string) { return { "handler": `${name}/index.handler`, From 9f1787cb71ed25d634905ed6a48f35743e274c6a Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Thu, 30 May 2019 07:32:24 -0700 Subject: [PATCH 18/25] Create cwd --- src/plugins/func/funcUtils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/func/funcUtils.test.ts b/src/plugins/func/funcUtils.test.ts index ca6f62d2..5efcb906 100644 --- a/src/plugins/func/funcUtils.test.ts +++ b/src/plugins/func/funcUtils.test.ts @@ -11,7 +11,7 @@ describe("Func Utils", () => { mockFs({ "serverless.yml": MockFactory.createTestServerlessYml(true), "src/plugins/func/funcHandler.txt": MockFactory.createTestHandler(), - }, {createCwd: false, createTmp: true}) + }, {createCwd: true, createTmp: true}) }); afterEach(() => { From 4d497b80d11e2192125f657e0308b7b84ded8c94 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Thu, 30 May 2019 07:50:04 -0700 Subject: [PATCH 19/25] Run tests in band, add mocked file to func plugin tests --- package.json | 2 +- src/plugins/func/azureFunc.test.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b038e446..a971a247 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "eslint src/**/*.ts", "lintfix": "eslint src/**/*.ts --fix", "pretest": "npm run lint", - "test": "jest --config jest.config.js", + "test": "jest --config jest.config.js --runInBand", "prebuild": "rm -rf lib/ && npm test", "build": "tsc" }, diff --git a/src/plugins/func/azureFunc.test.ts b/src/plugins/func/azureFunc.test.ts index 2d576a5b..47cdb71b 100644 --- a/src/plugins/func/azureFunc.test.ts +++ b/src/plugins/func/azureFunc.test.ts @@ -24,7 +24,8 @@ describe("Azure Func Plugin", () => { "index.js": "contents", "function.json": "contents", }, - "serverless.yml": MockFactory.createTestServerlessYml(true) + "serverless.yml": MockFactory.createTestServerlessYml(true), + "src/plugins/func/funcHandler.txt": MockFactory.createTestHandler(), }, {createCwd: true, createTmp: true}) }); From 8b339ebb501fc53a4a32f9758be71343b3e6d7dd Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Thu, 30 May 2019 07:55:08 -0700 Subject: [PATCH 20/25] Changed test to not spy on writeFileSync --- package.json | 2 +- src/plugins/func/funcUtils.test.ts | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index a971a247..b038e446 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "eslint src/**/*.ts", "lintfix": "eslint src/**/*.ts --fix", "pretest": "npm run lint", - "test": "jest --config jest.config.js --runInBand", + "test": "jest --config jest.config.js", "prebuild": "rm -rf lib/ && npm test", "build": "tsc" }, diff --git a/src/plugins/func/funcUtils.test.ts b/src/plugins/func/funcUtils.test.ts index 5efcb906..32e952d4 100644 --- a/src/plugins/func/funcUtils.test.ts +++ b/src/plugins/func/funcUtils.test.ts @@ -33,13 +33,7 @@ describe("Func Utils", () => { const originalSls = MockFactory.createTestServerlessYml(false, 2); const writeFileSync = jest.spyOn(fs, "writeFileSync"); FuncPluginUtils.updateFunctionsYml(updatedFunctions, originalSls); - const call = writeFileSync.mock.calls[0] - expect(call[0]).toBe("serverless.yml"); - const expected = MockFactory.createTestServerlessYml( - true, - MockFactory.createTestFunctionsMetadata(3) - ); - expect(call[1]).toBe(expected); + expect(originalSls.functions).toEqual(updatedFunctions); }); it("adds new function name to function handler", () => { From dc5afbd53fd9277736233a14af2aa52c311d5543 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Thu, 30 May 2019 09:29:18 -0700 Subject: [PATCH 21/25] Using inline handler script and templates directory --- src/plugins/func/azureFunc.test.ts | 17 +----- src/plugins/func/azureFunc.ts | 12 ++-- src/plugins/func/bindingTemplates/http.json | 19 +++++++ src/plugins/func/funcHandler.txt | 18 ------ src/plugins/func/funcUtils.test.ts | 40 +++++-------- src/plugins/func/funcUtils.ts | 63 ++++++++++----------- src/shared/utils.ts | 6 +- src/test/mockFactory.ts | 28 ++------- tsconfig.json | 3 +- 9 files changed, 81 insertions(+), 125 deletions(-) create mode 100644 src/plugins/func/bindingTemplates/http.json delete mode 100644 src/plugins/func/funcHandler.txt diff --git a/src/plugins/func/azureFunc.test.ts b/src/plugins/func/azureFunc.test.ts index 47cdb71b..6e8337f2 100644 --- a/src/plugins/func/azureFunc.test.ts +++ b/src/plugins/func/azureFunc.test.ts @@ -25,7 +25,6 @@ describe("Azure Func Plugin", () => { "function.json": "contents", }, "serverless.yml": MockFactory.createTestServerlessYml(true), - "src/plugins/func/funcHandler.txt": MockFactory.createTestHandler(), }, {createCwd: true, createTmp: true}) }); @@ -33,10 +32,6 @@ describe("Azure Func Plugin", () => { mockFs.restore(); }); - afterEach(() => { - jest.clearAllMocks(); - }); - it("returns with missing name", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); @@ -60,14 +55,12 @@ describe("Azure Func Plugin", () => { const functionName = "myFunction"; options["name"] = functionName; const plugin = new AzureFuncPlugin(sls, options); - const writeFileSpy = jest.spyOn(fs, "writeFileSync"); const mkdirSpy = jest.spyOn(fs, "mkdirSync"); await invokeHook(plugin, "func:add:add"); expect(mkdirSpy).toBeCalledWith(functionName); - const calls = (writeFileSpy as any).mock.calls; + const calls = (sls.utils.writeFileSync as any).mock.calls; expect(calls[0][0]).toBe(path.join(functionName, "index.js")); expect(calls[1][0]).toBe(path.join(functionName, "function.json")); - const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); expectedFunctionsYml[functionName] = MockFactory.createTestFunctionMetadata(functionName); expect(calls[2][0]).toBe("serverless.yml"); @@ -83,17 +76,12 @@ describe("Azure Func Plugin", () => { "index.js": "contents", "function.json": "contents", }, - "serverless.yml": MockFactory.createTestServerlessYml(true) }, {createCwd: true, createTmp: true}); }); afterAll(() => { mockFs.restore(); }); - - afterEach(() => { - jest.clearAllMocks(); - }); it("returns with missing name", async () => { const sls = MockFactory.createTestServerless(); @@ -118,13 +106,12 @@ describe("Azure Func Plugin", () => { const plugin = new AzureFuncPlugin(sls, options); const functionName = "function1"; options["name"] = functionName; - const writeFileSpy = jest.spyOn(fs, "writeFileSync"); const rimrafSpy = jest.spyOn(rimraf, "sync"); await invokeHook(plugin, "func:remove:remove"); expect(rimrafSpy).toBeCalledWith(functionName); const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); delete expectedFunctionsYml[functionName]; - expect(writeFileSpy).toBeCalledWith("serverless.yml", MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) + expect(sls.utils.writeFileSync).toBeCalledWith("serverless.yml", MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) }); }); }); \ No newline at end of file diff --git a/src/plugins/func/azureFunc.ts b/src/plugins/func/azureFunc.ts index 8474ee4a..823b585b 100644 --- a/src/plugins/func/azureFunc.ts +++ b/src/plugins/func/azureFunc.ts @@ -74,15 +74,15 @@ export class AzureFuncPlugin { private createFunctionDir(name: string) { this.serverless.cli.log("Creating function dir"); fs.mkdirSync(name); - fs.writeFileSync(path.join(name, "index.js"), FuncPluginUtils.getFunctionHandler(name)); - fs.writeFileSync(path.join(name, "function.json"), FuncPluginUtils.getFunctionJson(name, this.options)) + this.serverless.utils.writeFileSync(path.join(name, "index.js"), FuncPluginUtils.getFunctionHandler(name)); + this.serverless.utils.writeFileSync(path.join(name, "function.json"), FuncPluginUtils.getFunctionJsonString(name, this.options)) } private addToServerlessYml(name: string) { this.serverless.cli.log("Adding to serverless.yml"); - const functionYml = FuncPluginUtils.getFunctionsYml(); + const functionYml = FuncPluginUtils.getFunctionsYml(this.serverless); functionYml[name] = FuncPluginUtils.getFunctionSlsObject(name, this.options); - FuncPluginUtils.updateFunctionsYml(functionYml); + FuncPluginUtils.updateFunctionsYml(this.serverless, functionYml); } private async remove() { @@ -102,8 +102,8 @@ export class AzureFuncPlugin { } private async removeFromServerlessYml(name: string) { - const functionYml = FuncPluginUtils.getFunctionsYml(); + const functionYml = FuncPluginUtils.getFunctionsYml(this.serverless); delete functionYml[name]; - FuncPluginUtils.updateFunctionsYml(functionYml) + FuncPluginUtils.updateFunctionsYml(this.serverless, functionYml) } } \ No newline at end of file diff --git a/src/plugins/func/bindingTemplates/http.json b/src/plugins/func/bindingTemplates/http.json new file mode 100644 index 00000000..9f2d4d85 --- /dev/null +++ b/src/plugins/func/bindingTemplates/http.json @@ -0,0 +1,19 @@ +{ + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "name": "req", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "res" + } + ] +} \ No newline at end of file diff --git a/src/plugins/func/funcHandler.txt b/src/plugins/func/funcHandler.txt deleted file mode 100644 index a9154c1c..00000000 --- a/src/plugins/func/funcHandler.txt +++ /dev/null @@ -1,18 +0,0 @@ -"use strict"; - -module.exports.handler = async function (context, req) { - context.log("JavaScript HTTP trigger function processed a request."); - - if (req.query.name || (req.body && req.body.name)) { - context.res = { - // status: 200, /* Defaults to 200 */ - body: "${name} " + (req.query.name || req.body.name) - }; - } - else { - context.res = { - status: 400, - body: "Please pass a name on the query string or in the request body" - }; - } -}; \ No newline at end of file diff --git a/src/plugins/func/funcUtils.test.ts b/src/plugins/func/funcUtils.test.ts index 32e952d4..83480457 100644 --- a/src/plugins/func/funcUtils.test.ts +++ b/src/plugins/func/funcUtils.test.ts @@ -1,44 +1,32 @@ -import fs from "fs"; -import mockFs from "mock-fs"; import { MockFactory } from "../../test/mockFactory"; import { FuncPluginUtils } from "./funcUtils"; describe("Func Utils", () => { - - - - beforeAll(() => { - mockFs({ - "serverless.yml": MockFactory.createTestServerlessYml(true), - "src/plugins/func/funcHandler.txt": MockFactory.createTestHandler(), - }, {createCwd: true, createTmp: true}) - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - afterAll(() => { - mockFs.restore(); - }); - it("gets functions yml", () => { - const funcYaml = FuncPluginUtils.getFunctionsYml(); + const sls = MockFactory.createTestServerless(); + const funcYaml = FuncPluginUtils.getFunctionsYml(sls); expect(funcYaml).toEqual(MockFactory.createTestFunctionsMetadata()); }); it("updates functions yml", () => { const updatedFunctions = MockFactory.createTestFunctionsMetadata(3); const originalSls = MockFactory.createTestServerlessYml(false, 2); - const writeFileSync = jest.spyOn(fs, "writeFileSync"); - FuncPluginUtils.updateFunctionsYml(updatedFunctions, originalSls); - expect(originalSls.functions).toEqual(updatedFunctions); + const sls = MockFactory.createTestServerless(); + FuncPluginUtils.updateFunctionsYml(sls, updatedFunctions, originalSls); + const calls = (sls.utils.writeFileSync as any).mock.calls[0] + expect(calls[0]).toBe("serverless.yml"); + const expected = MockFactory.createTestServerlessYml( + true, + MockFactory.createTestFunctionsMetadata(3) + ); + expect(calls[1]).toBe(expected); }); it("adds new function name to function handler", () => { const name = "This is my function name" - expect(FuncPluginUtils.getFunctionHandler(name)) - .toContain(`body: '${name} ' + (req.query.name || req.body.name)`) + const handler = FuncPluginUtils.getFunctionHandler(name); + expect(handler) + .toContain(`body: "${name} " + (req.query.name || req.body.name)`); }); }); \ No newline at end of file diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts index 52c1fa5a..b38e5a41 100644 --- a/src/plugins/func/funcUtils.ts +++ b/src/plugins/func/funcUtils.ts @@ -1,54 +1,49 @@ -import fs from "fs"; import yaml from "js-yaml"; -import path from "path"; -import { Utils } from "../../shared/utils"; +import Serverless from "serverless"; +import httpBinding from "./bindingTemplates/http.json" export class FuncPluginUtils { - public static getServerlessYml() { - return yaml.safeLoad(fs.readFileSync("serverless.yml", "utf-8")); + public static getServerlessYml(sls: Serverless) { + return yaml.safeLoad(sls.utils.readFileSync("serverless.yml")); } - public static getFunctionsYml(serverlessYml?: any) { - serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(); + public static getFunctionsYml(sls: Serverless, serverlessYml?: any) { + serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(sls); return serverlessYml["functions"]; } - public static updateFunctionsYml(functionYml: any, serverlessYml?: any) { - serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(); + public static updateFunctionsYml(sls: Serverless, functionYml: any, serverlessYml?: any) { + serverlessYml = serverlessYml || FuncPluginUtils.getServerlessYml(sls); serverlessYml["functions"] = functionYml; - fs.writeFileSync("serverless.yml", yaml.dump(serverlessYml)); + sls.utils.writeFileSync("serverless.yml", yaml.dump(serverlessYml)); } public static getFunctionHandler(name: string) { - const filePath = path.resolve(process.cwd(), "src", "plugins", "func", "funcHandler.txt") - return Utils.interpolateFile(filePath, new Map([ - ["name", name] - ])); + return `"use strict"; + + module.exports.handler = async function (context, req) { + context.log("JavaScript HTTP trigger function processed a request."); + + if (req.query.name || (req.body && req.body.name)) { + context.res = { + // status: 200, /* Defaults to 200 */ + body: "${name} " + (req.query.name || req.body.name) + }; + } + else { + context.res = { + status: 400, + body: "Please pass a name on the query string or in the request body" + }; + } + };` } - public static getFunctionJson(name: string, options: any) { + public static getFunctionJsonString(name: string, options: any) { // TODO: This is where we would just generate function JSON from SLS object // using getFunctionSlsObject(name, options). Currently defaulting to http in and out - return JSON.stringify({ - "bindings": [ - { - "authLevel": "anonymous", - "type": "httpTrigger", - "direction": "in", - "name": "req", - "methods": [ - "get", - "post" - ] - }, - { - "type": "http", - "direction": "out", - "name": "res" - } - ] - }); + return JSON.stringify(httpBinding); } public static getFunctionSlsObject(name: string, options: any) { diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 65d58b36..ee4e3cfd 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -1,6 +1,6 @@ +import Serverless from "serverless"; import { BindingUtils } from "./bindings"; import { constants } from "./constants"; -import fs from 'fs'; export interface FunctionMetadata { entryPoint: any; @@ -112,8 +112,8 @@ export class Utils { return metaData; } - public static interpolateFile(path: string, params: Map) { - const template = fs.readFileSync(path, "utf8"); + public static interpolateFile(sls: Serverless, path: string, params: Map) { + const template = sls.utils.readFileSync(path); const names = params.keys(); const vals = params.values(); return new Function(...names, `return \`${template}\`;`)(...vals); diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index 83e77d34..ef1eedf5 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -1,9 +1,9 @@ import { AuthResponse, LinkedSubscription, TokenCredentialsBase } from "@azure/ms-rest-nodeauth"; +import yaml from "js-yaml"; import Serverless from "serverless"; import Service from "serverless/classes/Service"; import Utils = require("serverless/classes/Utils"); import PluginManager = require("serverless/classes/PluginManager"); -import yaml from "js-yaml" export class MockFactory { @@ -62,26 +62,6 @@ export class MockFactory { return (wrap) ? {"functions": data } : data; } - public static createTestHandler() { - // The mock-fs module doesn't allow for template/multi-line strings... - return "'use strict'\n" + - "module.exports.handler = async function (context, req) {\n" + - "\tcontext.log('JavaScript HTTP trigger function processed a request.');\n" + - "\tif (req.query.name || (req.body && req.body.name)) {\n" + - "\t\tcontext.res = {\n" + - "\t\t\t// status: 200, /* Defaults to 200 */\n" + - "\t\t\tbody: '${name} ' + (req.query.name || req.body.name)\n" + - "\t\t};\n" + - "\t}\n" + - "\telse {\n" + - "\t\tcontext.res = {\n" + - "\t\t\tstatus: 400,\n" + - "\t\t\tbody: 'Please pass a name on the query string or in the request body'\n" + - "\t\t};\n" + - "\t}\n" + - "};\n" - } - public static createTestFunctionMetadata(name: string) { return { "handler": `${name}/index.handler`, @@ -132,7 +112,11 @@ export class MockFactory { getVersion: jest.fn(), logStat: jest.fn(), readFile: jest.fn(), - readFileSync: jest.fn(), + readFileSync: jest.fn((filename) => { + if (filename === "serverless.yml") { + return MockFactory.createTestServerlessYml(true); + } + }), walkDirSync: jest.fn(), writeFile: jest.fn(), writeFileDir: jest.fn(), diff --git a/tsconfig.json b/tsconfig.json index d4d9522e..32ff8fa7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ }, "include": [ "src", - "src/**/*.json" + "src/**/*.json", + "src/**/*.txt" ] } \ No newline at end of file From 4245bb0b35e0e62f9549fc180e1c43caa6d68e08 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Thu, 30 May 2019 09:49:52 -0700 Subject: [PATCH 22/25] Update func util to use sls util read file --- serverless.yml | 36 ----------------------------------- src/plugins/func/funcUtils.ts | 34 ++++++++++++++++----------------- src/test/mockFactory.ts | 2 +- 3 files changed, 18 insertions(+), 54 deletions(-) delete mode 100644 serverless.yml diff --git a/serverless.yml b/serverless.yml deleted file mode 100644 index 3d4e61a2..00000000 --- a/serverless.yml +++ /dev/null @@ -1,36 +0,0 @@ -provider: - name: azure - location: West US 2 -plugins: - - serverless-azure-functions -functions: - function1: - handler: function1/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res - function2: - handler: function2/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res - function3: - handler: function3/index.handler - events: - - http: true - x-azure-settings: - authLevel: anonymous - - http: true - x-azure-settings: - direction: out - name: res diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts index b38e5a41..cde6031c 100644 --- a/src/plugins/func/funcUtils.ts +++ b/src/plugins/func/funcUtils.ts @@ -5,7 +5,7 @@ import httpBinding from "./bindingTemplates/http.json" export class FuncPluginUtils { public static getServerlessYml(sls: Serverless) { - return yaml.safeLoad(sls.utils.readFileSync("serverless.yml")); + return sls.utils.readFileSync("serverless.yml"); } public static getFunctionsYml(sls: Serverless, serverlessYml?: any) { @@ -22,22 +22,22 @@ export class FuncPluginUtils { public static getFunctionHandler(name: string) { return `"use strict"; - module.exports.handler = async function (context, req) { - context.log("JavaScript HTTP trigger function processed a request."); - - if (req.query.name || (req.body && req.body.name)) { - context.res = { - // status: 200, /* Defaults to 200 */ - body: "${name} " + (req.query.name || req.body.name) - }; - } - else { - context.res = { - status: 400, - body: "Please pass a name on the query string or in the request body" - }; - } - };` +module.exports.handler = async function (context, req) { + context.log("JavaScript HTTP trigger function processed a request."); + + if (req.query.name || (req.body && req.body.name)) { + context.res = { + // status: 200, /* Defaults to 200 */ + body: "${name} " + (req.query.name || req.body.name) + }; + } + else { + context.res = { + status: 400, + body: "Please pass a name on the query string or in the request body" + }; + } +};` } public static getFunctionJsonString(name: string, options: any) { diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index ef1eedf5..559143c5 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -114,7 +114,7 @@ export class MockFactory { readFile: jest.fn(), readFileSync: jest.fn((filename) => { if (filename === "serverless.yml") { - return MockFactory.createTestServerlessYml(true); + return MockFactory.createTestServerlessYml(); } }), walkDirSync: jest.fn(), From e5bf4da4387231f7765e472140e2fcfbb3551f35 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Thu, 30 May 2019 10:04:48 -0700 Subject: [PATCH 23/25] Remove text from tsconfig include --- tsconfig.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 32ff8fa7..d4d9522e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,6 @@ }, "include": [ "src", - "src/**/*.json", - "src/**/*.txt" + "src/**/*.json" ] } \ No newline at end of file From 136c97ac3b91501029ad43c8ab3616368ae77a16 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Thu, 30 May 2019 15:05:49 -0700 Subject: [PATCH 24/25] Respond to PR feedback --- src/plugins/func/azureFunc.test.ts | 4 ++-- src/plugins/func/azureFunc.ts | 6 +++++- src/plugins/func/funcUtils.ts | 4 ++-- src/test/mockFactory.ts | 6 +++--- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/plugins/func/azureFunc.test.ts b/src/plugins/func/azureFunc.test.ts index 6e8337f2..2ff32525 100644 --- a/src/plugins/func/azureFunc.test.ts +++ b/src/plugins/func/azureFunc.test.ts @@ -62,7 +62,7 @@ describe("Azure Func Plugin", () => { expect(calls[0][0]).toBe(path.join(functionName, "index.js")); expect(calls[1][0]).toBe(path.join(functionName, "function.json")); const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); - expectedFunctionsYml[functionName] = MockFactory.createTestFunctionMetadata(functionName); + expectedFunctionsYml[functionName] = MockFactory.createTestFunctionMetadata(); expect(calls[2][0]).toBe("serverless.yml"); expect(calls[2][1]).toBe(MockFactory.createTestServerlessYml(true, expectedFunctionsYml)); }); @@ -91,7 +91,7 @@ describe("Azure Func Plugin", () => { expect(sls.cli.log).toBeCalledWith("Need to provide a name of function to remove") }); - it("returns with pre-existing function", async () => { + it("returns with non-existing function", async () => { const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); options["name"] = "myNonExistingFunction"; diff --git a/src/plugins/func/azureFunc.ts b/src/plugins/func/azureFunc.ts index 823b585b..00d1a212 100644 --- a/src/plugins/func/azureFunc.ts +++ b/src/plugins/func/azureFunc.ts @@ -73,7 +73,11 @@ export class AzureFuncPlugin { private createFunctionDir(name: string) { this.serverless.cli.log("Creating function dir"); - fs.mkdirSync(name); + try { + fs.mkdirSync(name); + } catch (e) { + this.serverless.cli.log(`Error making directory ${e}`); + } this.serverless.utils.writeFileSync(path.join(name, "index.js"), FuncPluginUtils.getFunctionHandler(name)); this.serverless.utils.writeFileSync(path.join(name, "function.json"), FuncPluginUtils.getFunctionJsonString(name, this.options)) } diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts index cde6031c..4d1b92f4 100644 --- a/src/plugins/func/funcUtils.ts +++ b/src/plugins/func/funcUtils.ts @@ -43,7 +43,7 @@ module.exports.handler = async function (context, req) { public static getFunctionJsonString(name: string, options: any) { // TODO: This is where we would just generate function JSON from SLS object // using getFunctionSlsObject(name, options). Currently defaulting to http in and out - return JSON.stringify(httpBinding); + return JSON.stringify(httpBinding, null, 2); } public static getFunctionSlsObject(name: string, options: any) { @@ -52,7 +52,7 @@ module.exports.handler = async function (context, req) { private static defaultFunctionSlsObject(name: string) { return { - handler: `${name}/index.handler`, + handler: `index.handler`, events: FuncPluginUtils.httpEvents() } } diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index 559143c5..831bc030 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -57,14 +57,14 @@ export class MockFactory { const data = {}; for (let i = 0; i < functionCount; i++) { const functionName = `function${i+1}`; - data[functionName] = MockFactory.createTestFunctionMetadata(functionName) + data[functionName] = MockFactory.createTestFunctionMetadata() } return (wrap) ? {"functions": data } : data; } - public static createTestFunctionMetadata(name: string) { + public static createTestFunctionMetadata() { return { - "handler": `${name}/index.handler`, + "handler": "index.handler", "events": [ { "http": true, From 85d8cca05f16b88c94b37c1eda63b16b0b0bab9f Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Fri, 31 May 2019 13:41:34 -0700 Subject: [PATCH 25/25] Update string to not use template string --- src/plugins/func/funcUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/func/funcUtils.ts b/src/plugins/func/funcUtils.ts index 4d1b92f4..7d025d59 100644 --- a/src/plugins/func/funcUtils.ts +++ b/src/plugins/func/funcUtils.ts @@ -52,7 +52,7 @@ module.exports.handler = async function (context, req) { private static defaultFunctionSlsObject(name: string) { return { - handler: `index.handler`, + handler: "index.handler", events: FuncPluginUtils.httpEvents() } }