This repository was archived by the owner on Dec 9, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 160
feat: Func plugin to add/remove functions within function app #151
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
287c478
Added stubs for add and remove func plugins
tbarlow12 da5d06c
Removing from serverless yml and function directory
tbarlow12 544accf
Generating scaffolding code with add function
tbarlow12 7e22553
Mocking file system
tbarlow12 6882d73
Stub tests for func utils
tbarlow12 4b01f4c
Added tests for add and remove plugins
tbarlow12 ffebc30
Remove sample data
tbarlow12 6a74d86
Add docs
tbarlow12 552fce2
Remove junk files from test
tbarlow12 d32aef2
Update message in func plugin
tbarlow12 b518cd8
Removed jest functions and instead added spies
tbarlow12 acc4fa1
Update to double quotes
tbarlow12 c3a5c33
Remove unused import
tbarlow12 2bd21b2
Update with changes from dev
tbarlow12 c60fc50
Update func utils and tests to not use regex
tbarlow12 490e983
Fix bug in altering serverless yml
tbarlow12 ef2ee1b
Clearing all mocks after each test to avoid conflicts
tbarlow12 4f59c32
Added template file for generated javascript
tbarlow12 9f1787c
Create cwd
tbarlow12 4d497b8
Run tests in band, add mocked file to func plugin tests
tbarlow12 8b339eb
Changed test to not spy on writeFileSync
tbarlow12 dc5afbd
Using inline handler script and templates directory
tbarlow12 4245bb0
Update func util to use sls util read file
tbarlow12 e5bf4da
Remove text from tsconfig include
tbarlow12 136c97a
Respond to PR feedback
tbarlow12 85d8cca
Update string to not use template string
tbarlow12 49a0e2a
Include updates from dev branch
tbarlow12 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| import fs from "fs"; | ||
| import mockFs 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", () => { | ||
|
|
||
| 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", () => { | ||
|
|
||
| beforeAll(() => { | ||
| mockFs({ | ||
| "myExistingFunction": { | ||
| "index.js": "contents", | ||
| "function.json": "contents", | ||
| }, | ||
| "serverless.yml": MockFactory.createTestServerlessYml(true), | ||
| }, {createCwd: true, createTmp: true}) | ||
| }); | ||
|
|
||
| afterAll(() => { | ||
| mockFs.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: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 functionName = "myFunction"; | ||
| options["name"] = functionName; | ||
| const plugin = new AzureFuncPlugin(sls, options); | ||
| const mkdirSpy = jest.spyOn(fs, "mkdirSync"); | ||
| await invokeHook(plugin, "func:add:add"); | ||
| expect(mkdirSpy).toBeCalledWith(functionName); | ||
| 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(); | ||
| expect(calls[2][0]).toBe("serverless.yml"); | ||
| expect(calls[2][1]).toBe(MockFactory.createTestServerlessYml(true, expectedFunctionsYml)); | ||
| }); | ||
| }); | ||
|
|
||
| describe("Remove command", () => { | ||
|
|
||
| beforeAll(() => { | ||
| mockFs({ | ||
| "function1": { | ||
| "index.js": "contents", | ||
| "function.json": "contents", | ||
| }, | ||
| }, {createCwd: true, createTmp: true}); | ||
| }); | ||
|
|
||
| afterAll(() => { | ||
| mockFs.restore(); | ||
| }); | ||
|
|
||
| it("returns with missing name", async () => { | ||
| const sls = MockFactory.createTestServerless(); | ||
| const options = MockFactory.createTestServerlessOptions(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q: Would it make sense to add a default name to the options and then remove/override as needed? Just thinking if we'll need it for other function tests if we add more functionality, like the option to include the extension bundles
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For sure. Let's add that in another PR |
||
| 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 non-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; | ||
| const rimrafSpy = jest.spyOn(rimraf, "sync"); | ||
| await invokeHook(plugin, "func:remove:remove"); | ||
| expect(rimrafSpy).toBeCalledWith(functionName); | ||
| const expectedFunctionsYml = MockFactory.createTestFunctionsMetadata(); | ||
| delete expectedFunctionsYml[functionName]; | ||
| expect(sls.utils.writeFileSync).toBeCalledWith("serverless.yml", MockFactory.createTestServerlessYml(true, expectedFunctionsYml)) | ||
| }); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| import fs from "fs"; | ||
| import path from "path"; | ||
| import rimraf from "rimraf"; | ||
| import Serverless from "serverless"; | ||
| import { FuncPluginUtils } from "./funcUtils"; | ||
|
|
||
| export class AzureFuncPlugin { | ||
tbarlow12 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| public hooks: { [eventName: string]: Promise<any> }; | ||
| public commands: any; | ||
|
|
||
|
|
||
| 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 = { | ||
| func: { | ||
| usage: "Add or remove functions", | ||
| 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", | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| 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; | ||
mydiemho marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| 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"); | ||
| 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)) | ||
| } | ||
|
|
||
| private addToServerlessYml(name: string) { | ||
| this.serverless.cli.log("Adding to serverless.yml"); | ||
| const functionYml = FuncPluginUtils.getFunctionsYml(this.serverless); | ||
| functionYml[name] = FuncPluginUtils.getFunctionSlsObject(name, this.options); | ||
| FuncPluginUtils.updateFunctionsYml(this.serverless, 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(this.serverless); | ||
| delete functionYml[name]; | ||
| FuncPluginUtils.updateFunctionsYml(this.serverless, functionYml) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| { | ||
| "bindings": [ | ||
| { | ||
| "authLevel": "anonymous", | ||
| "type": "httpTrigger", | ||
| "direction": "in", | ||
| "name": "req", | ||
| "methods": [ | ||
| "get", | ||
| "post" | ||
| ] | ||
| }, | ||
| { | ||
| "type": "http", | ||
| "direction": "out", | ||
| "name": "res" | ||
| } | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { MockFactory } from "../../test/mockFactory"; | ||
| import { FuncPluginUtils } from "./funcUtils"; | ||
|
|
||
| describe("Func Utils", () => { | ||
|
|
||
| it("gets functions yml", () => { | ||
| 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 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" | ||
| const handler = FuncPluginUtils.getFunctionHandler(name); | ||
| expect(handler) | ||
| .toContain(`body: "${name} " + (req.query.name || req.body.name)`); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.