This repository has been archived by the owner on Dec 9, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 161
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Func plugin to add/remove functions within function app (#151)
- Loading branch information
Showing
11 changed files
with
444 additions
and
10 deletions.
There are no files selected for viewing
This file contains 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 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 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 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(); | ||
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 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 { | ||
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; | ||
} | ||
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 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 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.