From 56ff7adc53fb18df99a1acf8b8951e928a72ff5e Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Wed, 20 Jul 2022 22:25:52 +0800 Subject: [PATCH 01/16] =?UTF-8?q?=E2=9C=A8=20feat:=20enable=20asycn=20func?= =?UTF-8?q?tion=20plugin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yad <459647480@qq.com> --- src/main.ts | 8 +++- src/openfunction/async_server.ts | 28 +++++++++++++- src/options.ts | 23 ++++++++++++ src/plugin.ts | 64 ++++++++++++++++++++++++++++++++ src/plugin_context.ts | 46 +++++++++++++++++++++++ 5 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 src/plugin.ts create mode 100644 src/plugin_context.ts diff --git a/src/main.ts b/src/main.ts index 1622a9c5..f0e76fd6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,6 +26,7 @@ import { OpenFunctionContext, ContextUtils, } from './openfunction/function_context'; +import {getPlugins} from './plugin'; /** * Main entrypoint for the functions framework that loads the user's function @@ -50,13 +51,16 @@ export const main = async () => { process.exit(1); } const {userFunction, signatureType} = loadedFunction; - + if (options.plugin) { + options.plugin = await getPlugins(options.sourceLocation, options.plugin); + } if (ContextUtils.IsAsyncRuntime(options.context as OpenFunctionContext)) { options.context!.port = options.port; const server = getAysncServer( userFunction! as OpenFunction, - options.context! + options.context!, + options.plugin ); await server.start(); } else { diff --git a/src/openfunction/async_server.ts b/src/openfunction/async_server.ts index 358b9ae9..90856ed7 100644 --- a/src/openfunction/async_server.ts +++ b/src/openfunction/async_server.ts @@ -5,6 +5,7 @@ import {OpenFunction} from '../functions'; import {OpenFunctionContext, ContextUtils} from './function_context'; import {OpenFunctionRuntime} from './function_runtime'; +import {PluginContext, PluginContextRuntime} from '../plugin_context'; export type AsyncFunctionServer = DaprServer; @@ -17,13 +18,38 @@ export type AsyncFunctionServer = DaprServer; */ export default function ( userFunction: OpenFunction, - context: OpenFunctionContext + context: OpenFunctionContext, + pluginContext?: PluginContext ): AsyncFunctionServer { const app = new DaprServer('localhost', context.port); const ctx = OpenFunctionRuntime.ProxyContext(context); const wrapper = async (data: object) => { + const runtime: PluginContextRuntime | undefined = pluginContext + ? { + pluginContext: pluginContext, + data: data, + context: ctx, + } + : undefined; + + if (runtime) { + runtime.context = ctx; + runtime.data = data; + for (let i = 0; i < runtime.pluginContext.prePluginFuncs!.length; i++) { + await runtime.pluginContext.prePluginFuncs![i](pluginContext); + data = runtime.data; + } + } await userFunction(ctx, data); + if (runtime) { + runtime.context = ctx; + runtime.data = data; + for (let i = 0; i < runtime.pluginContext.postPluginFuncs!.length; i++) { + await runtime.pluginContext.postPluginFuncs![i](pluginContext); + data = runtime.data; + } + } }; // Initialize the server with the user's function. diff --git a/src/options.ts b/src/options.ts index c3611304..f96855c8 100644 --- a/src/options.ts +++ b/src/options.ts @@ -19,6 +19,7 @@ import * as minimist from 'minimist'; import {SignatureType, isValidSignatureType} from './types'; import {OpenFunctionContext} from './openfunction/function_context'; +import {PluginContext} from './plugin_context'; const debug = Debug('common:options'); @@ -53,6 +54,10 @@ export interface FrameworkOptions { * The context to use for the function when serving. */ context?: OpenFunctionContext; + /** + * The context to use for the function when serving. + */ + plugin?: PluginContext; /** * Whether or not the --help CLI flag was provided. */ @@ -134,7 +139,23 @@ const FunctionContextOption = new ConfigurableOption( } } ); +const PluginContextOption = new ConfigurableOption( + 'plugin', + 'PLUGIN_CONTEXT', + undefined, + x => { + // Try to parse plugin string + debug('ℹ️ Plugin loaded: %s', x); + try { + const context = JSON.parse(x); + return context as PluginContext; + } catch (e) { + debug('Failed to parse plugin: %s', e); + return undefined; + } + } +); export const helpText = `Example usage: functions-framework --target=helloWorld --port=8080 Documentation: @@ -158,6 +179,7 @@ export const parseOptions = ( SignatureOption.cliOption, SourceLocationOption.cliOption, FunctionContextOption.cliOption, + PluginContextOption.cliOption, ], }); return { @@ -166,6 +188,7 @@ export const parseOptions = ( sourceLocation: SourceLocationOption.parse(argv, envVars), signatureType: SignatureOption.parse(argv, envVars), context: FunctionContextOption.parse(argv, envVars), + plugin: PluginContextOption.parse(argv, envVars), printHelp: cliArgs[2] === '-h' || cliArgs[2] === '--help', }; }; diff --git a/src/plugin.ts b/src/plugin.ts new file mode 100644 index 00000000..c89e2856 --- /dev/null +++ b/src/plugin.ts @@ -0,0 +1,64 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import {PluginContext} from './plugin_context'; + +export async function loadPlugins( + codeLocation: string +): Promise> { + const funcMap: Map = new Map(); + const param = path.resolve(`${codeLocation}/plugins`); + const plugin_files: Array = []; + const files = fs.readdirSync(param); + files.forEach(f => { + if (f.endsWith('.js')) { + plugin_files.push(path.join(param, f)); + } + }); + console.log(plugin_files); + plugin_files.forEach(f => { + const pluginModule = require(f); + Object.keys(pluginModule).forEach(key => { + funcMap.set(key, pluginModule[key]); + }); + }); + return funcMap; +} + +export async function getPlugins( + codeLocation: string, + pluginContext: PluginContext +): Promise { + const funcMapResult: Map = new Map(); + const funcMap = await loadPlugins(codeLocation); + pluginContext.pluginMap = funcMap; + pluginContext.prePluginFuncs = []; + pluginContext.postPluginFuncs = []; + pluginContext.prePlugins.forEach(p => { + const func = funcMap.get(p); + if (func) { + funcMapResult.set(p, func); + pluginContext.prePluginFuncs!.push(func); + } else { + console.error('----------------error----------------'); + console.error( + `pre-plugin-function[${p}] is not found \nDid you specify the correct target plugin-function to execute?` + ); + console.error('-------------------------------------'); + } + }); + pluginContext.postPlugins.forEach(p => { + const func = funcMap.get(p); + if (func) { + funcMapResult.set(p, func); + pluginContext.postPluginFuncs!.push(func); + } else { + console.error('----------------error----------------'); + console.error( + `post-plugin-function[${p}] is not found \nDid you specify the correct target plugin-function to execute?` + ); + console.error('-------------------------------------'); + } + }); + + return pluginContext; +} diff --git a/src/plugin_context.ts b/src/plugin_context.ts new file mode 100644 index 00000000..8adfffa5 --- /dev/null +++ b/src/plugin_context.ts @@ -0,0 +1,46 @@ +import {OpenFunctionRuntime} from './functions'; +/** + * The OpenFunction's plugin context. + * @public + */ +export interface PluginContext { + /** + * The name of the pre plugins. + */ + prePlugins: Array; + /** + * The name of the pre plugins. + */ + postPlugins: Array; + /** + * The func of the pre plugins. + */ + prePluginFuncs?: Array; + /** + * The func of the pre plugins. + */ + postPluginFuncs?: Array; + /** + * The refect between name func. + */ + pluginMap?: Map; +} + +/** + * The OpenFunction's plugin context runtime. + * @public + */ +export interface PluginContextRuntime { + /** + * The refect between name func. + */ + pluginContext: PluginContext; + /** + * OpenFunctionRuntime. + */ + context?: OpenFunctionRuntime; + /** + * data. + */ + data?: object; +} From 10c240fb5e852e9a661002cd8aaec31a7c7c3892 Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Fri, 22 Jul 2022 16:36:35 +0800 Subject: [PATCH 02/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20enable=20?= =?UTF-8?q?aysnc=20function=20plugin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yad <459647480@qq.com> --- src/main.ts | 13 ++-- src/openfunction/async_server.ts | 47 +++++++------- src/openfunction/function_context.ts | 76 ++++++++++++++++++++++ src/options.ts | 24 ------- src/plugin.ts | 95 ++++++++++++++-------------- src/plugin_context.ts | 46 -------------- 6 files changed, 154 insertions(+), 147 deletions(-) delete mode 100644 src/plugin_context.ts diff --git a/src/main.ts b/src/main.ts index f0e76fd6..baf85efb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,7 +26,7 @@ import { OpenFunctionContext, ContextUtils, } from './openfunction/function_context'; -import {getPlugins} from './plugin'; +import {loadPlugins} from './plugin'; /** * Main entrypoint for the functions framework that loads the user's function @@ -51,16 +51,17 @@ export const main = async () => { process.exit(1); } const {userFunction, signatureType} = loadedFunction; - if (options.plugin) { - options.plugin = await getPlugins(options.sourceLocation, options.plugin); + if (options.context) { + options.context.pluginMap = await loadPlugins( + options.sourceLocation, + options.context + ); } if (ContextUtils.IsAsyncRuntime(options.context as OpenFunctionContext)) { options.context!.port = options.port; - const server = getAysncServer( userFunction! as OpenFunction, - options.context!, - options.plugin + options.context! ); await server.start(); } else { diff --git a/src/openfunction/async_server.ts b/src/openfunction/async_server.ts index 90856ed7..7ee96083 100644 --- a/src/openfunction/async_server.ts +++ b/src/openfunction/async_server.ts @@ -3,9 +3,13 @@ import {DaprServer} from '@dapr/dapr'; import {OpenFunction} from '../functions'; -import {OpenFunctionContext, ContextUtils} from './function_context'; +import { + OpenFunctionContext, + ContextUtils, + PluginContextRuntime, + Plugin, +} from './function_context'; import {OpenFunctionRuntime} from './function_runtime'; -import {PluginContext, PluginContextRuntime} from '../plugin_context'; export type AsyncFunctionServer = DaprServer; @@ -18,36 +22,33 @@ export type AsyncFunctionServer = DaprServer; */ export default function ( userFunction: OpenFunction, - context: OpenFunctionContext, - pluginContext?: PluginContext + context: OpenFunctionContext ): AsyncFunctionServer { const app = new DaprServer('localhost', context.port); const ctx = OpenFunctionRuntime.ProxyContext(context); const wrapper = async (data: object) => { - const runtime: PluginContextRuntime | undefined = pluginContext - ? { - pluginContext: pluginContext, - data: data, - context: ctx, + const runtime: PluginContextRuntime = { + context: context, + data: data, + }; + if (context.prePlugins) { + for (let i = 0; i < context.prePlugins!.length; i++) { + const p = context.pluginMap?.get(context.prePlugins![i]); + if (p) { + await p.execPreHook(runtime, context.pluginMap!); + data = runtime.data; } - : undefined; - - if (runtime) { - runtime.context = ctx; - runtime.data = data; - for (let i = 0; i < runtime.pluginContext.prePluginFuncs!.length; i++) { - await runtime.pluginContext.prePluginFuncs![i](pluginContext); - data = runtime.data; } } await userFunction(ctx, data); - if (runtime) { - runtime.context = ctx; - runtime.data = data; - for (let i = 0; i < runtime.pluginContext.postPluginFuncs!.length; i++) { - await runtime.pluginContext.postPluginFuncs![i](pluginContext); - data = runtime.data; + if (context.postPlugins) { + for (let i = 0; i < context.postPlugins!.length; i++) { + const p = context.pluginMap?.get(context.postPlugins![i]); + if (p) { + await p.execPostHook(runtime, context.pluginMap!); + data = runtime.data; + } } } }; diff --git a/src/openfunction/function_context.ts b/src/openfunction/function_context.ts index 450d7c07..967f9c1d 100644 --- a/src/openfunction/function_context.ts +++ b/src/openfunction/function_context.ts @@ -30,6 +30,18 @@ export interface OpenFunctionContext { * Optional output binding object. */ outputs?: OpenFunctionBinding; + /** + * Optional pre function exec plugins. + */ + prePlugins?: Array; + /** + * Optional post function exec plugins. + */ + postPlugins?: Array; + /** + * the map of plugin name & class. + */ + pluginMap?: Map; } /** @@ -139,3 +151,67 @@ export class ContextUtils { return component?.componentType.split('.')[0] === ComponentType.PubSub; } } + +/** + * The OpenFunction's plugin template. + * @public + */ +export class Plugin { + /** + * The plugin init method. + * @method + */ + async init() { + console.log('init'); + } + /** + * The plugin pre hook. + * @method + */ + async execPreHook(ctx: PluginContextRuntime, plugins: Map) { + console.log(ctx, plugins); + } + /** + * The plugin post hook. + * @method + */ + async execPostHook(ctx: PluginContextRuntime, plugins: Map) { + console.log(ctx, plugins); + } + /** + * get plugin filed. + * @method + */ + get(filedName: string) { + return filedName; + } + /** + * get plugin name. + * @method + */ + pluginName(): string { + return ''; + } + /** + * get plugin version. + * @method + */ + pluginVersion(): string { + return ''; + } +} + +/** + * The OpenFunction's plugin context runtime. + * @public + */ +export interface PluginContextRuntime { + /** + * OpenFunctionRuntime. + */ + context: OpenFunctionContext; + /** + * data. + */ + data: object; +} diff --git a/src/options.ts b/src/options.ts index f96855c8..3f4bb726 100644 --- a/src/options.ts +++ b/src/options.ts @@ -19,7 +19,6 @@ import * as minimist from 'minimist'; import {SignatureType, isValidSignatureType} from './types'; import {OpenFunctionContext} from './openfunction/function_context'; -import {PluginContext} from './plugin_context'; const debug = Debug('common:options'); @@ -54,10 +53,6 @@ export interface FrameworkOptions { * The context to use for the function when serving. */ context?: OpenFunctionContext; - /** - * The context to use for the function when serving. - */ - plugin?: PluginContext; /** * Whether or not the --help CLI flag was provided. */ @@ -139,23 +134,6 @@ const FunctionContextOption = new ConfigurableOption( } } ); -const PluginContextOption = new ConfigurableOption( - 'plugin', - 'PLUGIN_CONTEXT', - undefined, - x => { - // Try to parse plugin string - debug('ℹ️ Plugin loaded: %s', x); - - try { - const context = JSON.parse(x); - return context as PluginContext; - } catch (e) { - debug('Failed to parse plugin: %s', e); - return undefined; - } - } -); export const helpText = `Example usage: functions-framework --target=helloWorld --port=8080 Documentation: @@ -179,7 +157,6 @@ export const parseOptions = ( SignatureOption.cliOption, SourceLocationOption.cliOption, FunctionContextOption.cliOption, - PluginContextOption.cliOption, ], }); return { @@ -188,7 +165,6 @@ export const parseOptions = ( sourceLocation: SourceLocationOption.parse(argv, envVars), signatureType: SignatureOption.parse(argv, envVars), context: FunctionContextOption.parse(argv, envVars), - plugin: PluginContextOption.parse(argv, envVars), printHelp: cliArgs[2] === '-h' || cliArgs[2] === '--help', }; }; diff --git a/src/plugin.ts b/src/plugin.ts index c89e2856..276a71a7 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,11 +1,18 @@ import * as path from 'path'; import * as fs from 'fs'; -import {PluginContext} from './plugin_context'; - +import {OpenFunctionContext, Plugin} from './openfunction/function_context'; +import {forEach} from 'lodash'; export async function loadPlugins( - codeLocation: string -): Promise> { - const funcMap: Map = new Map(); + codeLocation: string, + context: OpenFunctionContext +): Promise> { + const pluginMap: Map = new Map(); + if (!context) { + return pluginMap; + } + if (!context.prePlugins && !context.postPlugins) { + return pluginMap; + } const param = path.resolve(`${codeLocation}/plugins`); const plugin_files: Array = []; const files = fs.readdirSync(param); @@ -14,51 +21,43 @@ export async function loadPlugins( plugin_files.push(path.join(param, f)); } }); + const set = new Set(); + if (context.prePlugins) { + forEach(context.prePlugins, value => { + set.add(value); + }); + } + if (context.postPlugins) { + forEach(context.postPlugins, value => { + set.add(value); + }); + } console.log(plugin_files); plugin_files.forEach(f => { - const pluginModule = require(f); - Object.keys(pluginModule).forEach(key => { - funcMap.set(key, pluginModule[key]); - }); - }); - return funcMap; -} - -export async function getPlugins( - codeLocation: string, - pluginContext: PluginContext -): Promise { - const funcMapResult: Map = new Map(); - const funcMap = await loadPlugins(codeLocation); - pluginContext.pluginMap = funcMap; - pluginContext.prePluginFuncs = []; - pluginContext.postPluginFuncs = []; - pluginContext.prePlugins.forEach(p => { - const func = funcMap.get(p); - if (func) { - funcMapResult.set(p, func); - pluginContext.prePluginFuncs!.push(func); - } else { - console.error('----------------error----------------'); - console.error( - `pre-plugin-function[${p}] is not found \nDid you specify the correct target plugin-function to execute?` - ); - console.error('-------------------------------------'); + try { + const pluginModule = require(f); + const p = new pluginModule(); + if ( + p.pluginName && + p.pluginVersion && + p.get && + p.execPreHook && + p.execPostHook && + p.init && + set.has(p.pluginName()) + ) { + pluginMap.set(p.pluginName(), p as Plugin); + } + } catch (error) { + console.error(error); } }); - pluginContext.postPlugins.forEach(p => { - const func = funcMap.get(p); - if (func) { - funcMapResult.set(p, func); - pluginContext.postPluginFuncs!.push(func); - } else { - console.error('----------------error----------------'); - console.error( - `post-plugin-function[${p}] is not found \nDid you specify the correct target plugin-function to execute?` - ); - console.error('-------------------------------------'); - } - }); - - return pluginContext; + try { + pluginMap.forEach(async item => { + await item.init(); + }); + } catch (error) { + console.error(error); + } + return pluginMap; } diff --git a/src/plugin_context.ts b/src/plugin_context.ts deleted file mode 100644 index 8adfffa5..00000000 --- a/src/plugin_context.ts +++ /dev/null @@ -1,46 +0,0 @@ -import {OpenFunctionRuntime} from './functions'; -/** - * The OpenFunction's plugin context. - * @public - */ -export interface PluginContext { - /** - * The name of the pre plugins. - */ - prePlugins: Array; - /** - * The name of the pre plugins. - */ - postPlugins: Array; - /** - * The func of the pre plugins. - */ - prePluginFuncs?: Array; - /** - * The func of the pre plugins. - */ - postPluginFuncs?: Array; - /** - * The refect between name func. - */ - pluginMap?: Map; -} - -/** - * The OpenFunction's plugin context runtime. - * @public - */ -export interface PluginContextRuntime { - /** - * The refect between name func. - */ - pluginContext: PluginContext; - /** - * OpenFunctionRuntime. - */ - context?: OpenFunctionRuntime; - /** - * data. - */ - data?: object; -} From c2146eeafb9cee7ea48d682967fc76d75b4fffec Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Fri, 22 Jul 2022 16:43:31 +0800 Subject: [PATCH 03/16] =?UTF-8?q?=F0=9F=8E=A8=20formate=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yad <459647480@qq.com> --- src/openfunction/async_server.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/openfunction/async_server.ts b/src/openfunction/async_server.ts index 7ee96083..380cd979 100644 --- a/src/openfunction/async_server.ts +++ b/src/openfunction/async_server.ts @@ -7,7 +7,6 @@ import { OpenFunctionContext, ContextUtils, PluginContextRuntime, - Plugin, } from './function_context'; import {OpenFunctionRuntime} from './function_runtime'; From 7c1d9e8c84ada7587d6b9513ba8b8471082c0217 Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Sat, 23 Jul 2022 16:48:57 +0800 Subject: [PATCH 04/16] =?UTF-8?q?=F0=9F=8E=A8=20improve=20aysnc=20function?= =?UTF-8?q?=20plugin=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yad <459647480@qq.com> --- .eslintignore | 1 + docs/generated/api.json | 252 ++++++++++++++++++++++++++ docs/generated/api.md | 10 + src/loader.ts | 84 +++++++++ src/main.ts | 16 +- src/openfunction/async_server.ts | 43 ++--- src/openfunction/function_context.ts | 69 ++----- src/plugin.ts | 63 ------- test/data/plugins/errorMissAll.js | 6 + test/data/plugins/errorMissGet.js | 13 ++ test/data/plugins/errorMissName.js | 22 +++ test/data/plugins/errorMissPost.js | 17 ++ test/data/plugins/errorMissPre.js | 17 ++ test/data/plugins/errorMissVersion.js | 22 +++ test/data/plugins/plugindemo.js | 33 ++++ test/integration/async_server.ts | 5 +- test/integration/test.ts | 204 +++++++++++++++++++++ test/loader.ts | 205 +++++++++++++++++++++ test/options.ts | 72 ++++++++ 19 files changed, 1003 insertions(+), 151 deletions(-) delete mode 100644 src/plugin.ts create mode 100644 test/data/plugins/errorMissAll.js create mode 100644 test/data/plugins/errorMissGet.js create mode 100644 test/data/plugins/errorMissName.js create mode 100644 test/data/plugins/errorMissPost.js create mode 100644 test/data/plugins/errorMissPre.js create mode 100644 test/data/plugins/errorMissVersion.js create mode 100644 test/data/plugins/plugindemo.js create mode 100644 test/integration/test.ts diff --git a/.eslintignore b/.eslintignore index c73a9699..93d966f5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,5 @@ **/node_modules build/ test/data/esm_* +test/data/plugins docs/ diff --git a/docs/generated/api.json b/docs/generated/api.json index e37b3605..f315a66f 100644 --- a/docs/generated/api.json +++ b/docs/generated/api.json @@ -1913,6 +1913,88 @@ "endIndex": 2 } }, + { + "kind": "PropertySignature", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionContext#postPlugins:member", + "docComment": "/**\n * Optional post function exec plugins.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "postPlugins?: " + }, + { + "kind": "Reference", + "text": "Array", + "canonicalReference": "!Array:interface" + }, + { + "kind": "Content", + "text": "" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": true, + "releaseTag": "Public", + "name": "postPlugins", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 5 + } + }, + { + "kind": "PropertySignature", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionContext#prePlugins:member", + "docComment": "/**\n * Optional pre function exec plugins.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "prePlugins?: " + }, + { + "kind": "Reference", + "text": "Array", + "canonicalReference": "!Array:interface" + }, + { + "kind": "Content", + "text": "" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": true, + "releaseTag": "Public", + "name": "prePlugins", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 5 + } + }, { "kind": "PropertySignature", "canonicalReference": "@openfunction/functions-framework!OpenFunctionContext#runtime:member", @@ -2489,6 +2571,176 @@ ], "implementsTokenRanges": [] }, + { + "kind": "Class", + "canonicalReference": "@openfunction/functions-framework!Plugin_2:class", + "docComment": "/**\n * The OpenFunction's plugin template.\n *\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare class Plugin " + } + ], + "releaseTag": "Public", + "name": "Plugin_2", + "preserveMemberOrder": false, + "members": [ + { + "kind": "Method", + "canonicalReference": "@openfunction/functions-framework!Plugin_2#execPostHook:member(1)", + "docComment": "/**\n * post main function exec.\n *\n * @param ctx - The openfunction runtime .\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "execPostHook(ctx?: " + }, + { + "kind": "Reference", + "text": "OpenFunctionRuntime", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime:class" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Reference", + "text": "Promise", + "canonicalReference": "!Promise:interface" + }, + { + "kind": "Content", + "text": "" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 3, + "endIndex": 5 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "ctx", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": true + } + ], + "isOptional": false, + "name": "execPostHook" + }, + { + "kind": "Method", + "canonicalReference": "@openfunction/functions-framework!Plugin_2#execPreHook:member(1)", + "docComment": "/**\n * pre main function exec.\n *\n * @param ctx - The openfunction runtime .\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "execPreHook(ctx?: " + }, + { + "kind": "Reference", + "text": "OpenFunctionRuntime", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime:class" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Reference", + "text": "Promise", + "canonicalReference": "!Promise:interface" + }, + { + "kind": "Content", + "text": "" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 3, + "endIndex": 5 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "ctx", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": true + } + ], + "isOptional": false, + "name": "execPreHook" + }, + { + "kind": "Method", + "canonicalReference": "@openfunction/functions-framework!Plugin_2#get:member(1)", + "docComment": "/**\n * get instance filed value.\n *\n * @param filedName - the instace filedName\n *\n * @returns filed value.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "get(filedName: " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "filedName", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + } + ], + "isOptional": false, + "name": "get" + } + ], + "implementsTokenRanges": [] + }, { "kind": "Interface", "canonicalReference": "@openfunction/functions-framework!Request_2:interface", diff --git a/docs/generated/api.md b/docs/generated/api.md index 4d25f565..565e4721 100644 --- a/docs/generated/api.md +++ b/docs/generated/api.md @@ -136,6 +136,8 @@ export interface OpenFunctionContext { name: string; outputs?: OpenFunctionBinding; port?: string; + postPlugins?: Array; + prePlugins?: Array; runtime: `${RuntimeType}` | `${Capitalize}` | `${Uppercase}`; version: string; } @@ -158,6 +160,14 @@ export abstract class OpenFunctionRuntime { protected trigger?: OpenFunctionTrigger; } +// @public +class Plugin_2 { + execPostHook(ctx?: OpenFunctionRuntime): Promise; + execPreHook(ctx?: OpenFunctionRuntime): Promise; + get(filedName: string): string; +} +export { Plugin_2 as Plugin } + // @public (undocumented) interface Request_2 extends Request_3 { rawBody?: Buffer; diff --git a/src/loader.ts b/src/loader.ts index 99550fe2..e866eb55 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -21,10 +21,13 @@ import * as path from 'path'; import * as semver from 'semver'; import * as readPkgUp from 'read-pkg-up'; +import * as fs from 'fs'; import {pathToFileURL} from 'url'; import {HandlerFunction} from './functions'; import {SignatureType} from './types'; import {getRegisteredFunction} from './function_registry'; +import {Plugin} from './openfunction/function_context'; +import {FrameworkOptions} from './options'; // Dynamic import function required to load user code packaged as an // ES module is only available on Node.js v13.2.0 and up. @@ -92,6 +95,7 @@ export async function getUserFunction( } | null> { try { const functionModulePath = getFunctionModulePath(codeLocation); + console.log(functionModulePath); if (functionModulePath === null) { console.error('Provided code is not a loadable module.'); return null; @@ -193,3 +197,83 @@ function getFunctionModulePath(codeLocation: string): string | null { } return path; } + +/** + * Returns user's plugin from function file. + * Returns null if plugin can't be retrieved. + * @return User's plugins or null. + */ +export async function getUserPlugins( + options: FrameworkOptions +): Promise { + // get plugin set + const pluginSet: Set = new Set(); + if ( + options.context && + options.context.prePlugins && + options.context.postPlugins + ) { + options.context.prePlugins.forEach(item => { + typeof item === 'string' && pluginSet.add(item); + }); + options.context.postPlugins.forEach(item => { + typeof item === 'string' && pluginSet.add(item); + }); + + try { + // load plugin js files + const instances: Map = new Map(); + const param = path.resolve(`${options.sourceLocation}/plugins`); + const plugin_files: Array = []; + const files = fs.readdirSync(param); + + for (const k in files) { + plugin_files.push(require.resolve(path.join(param, files[k]))); + } + + // find plugins class + const tempMap: Map = new Map(); + for (const k in plugin_files) { + const jsMoulde = require(plugin_files[k]); + if (jsMoulde && jsMoulde.Name) { + tempMap.set(jsMoulde.Name, jsMoulde); + } + } + + // instance plugin dynamic set ofn_plugin_name + const arr = Array.from(pluginSet.values()); + for (const k in arr) { + const module = tempMap.get(arr[k]); + if (module) { + const instance = new module(); + instance['ofn_plugin_name'] = module.Name; + instance['ofn_plugin_version'] = module.Version + ? module.Version + : 'v1'; + instances.set(arr[k], instance as Plugin); + } + } + + const prePlugins: Array = []; + const postPlugins: Array = []; + options.context.prePlugins.forEach(item => { + if (typeof item === 'string') { + const instance = instances.get(item); + typeof instance === 'object' && prePlugins.push(instance); + } + }); + options.context.postPlugins.forEach(item => { + if (typeof item === 'string') { + const instance = instances.get(item); + typeof instance === 'object' && postPlugins.push(instance); + } + }); + + options.context.prePlugins = prePlugins; + options.context.postPlugins = postPlugins; + } catch (error) { + console.error(error); + } + } + return options; +} diff --git a/src/main.ts b/src/main.ts index baf85efb..41686ae3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,7 +16,7 @@ // Functions framework entry point that configures and starts Node.js server // that runs user's code on HTTP request. -import {getUserFunction} from './loader'; +import {getUserFunction, getUserPlugins} from './loader'; import {ErrorHandler} from './invoker'; import {getServer} from './server'; import {parseOptions, helpText, OptionsError} from './options'; @@ -26,7 +26,6 @@ import { OpenFunctionContext, ContextUtils, } from './openfunction/function_context'; -import {loadPlugins} from './plugin'; /** * Main entrypoint for the functions framework that loads the user's function @@ -40,6 +39,7 @@ export const main = async () => { console.error(helpText); return; } + const loadedFunction = await getUserFunction( options.sourceLocation, options.target, @@ -51,19 +51,17 @@ export const main = async () => { process.exit(1); } const {userFunction, signatureType} = loadedFunction; - if (options.context) { - options.context.pluginMap = await loadPlugins( - options.sourceLocation, - options.context - ); - } + if (ContextUtils.IsAsyncRuntime(options.context as OpenFunctionContext)) { options.context!.port = options.port; const server = getAysncServer( userFunction! as OpenFunction, options.context! ); - await server.start(); + server.start().then(async () => { + // load and instance Plugins + await getUserPlugins(options); + }); } else { const server = getServer(userFunction!, signatureType, options.context); const errorHandler = new ErrorHandler(server); diff --git a/src/openfunction/async_server.ts b/src/openfunction/async_server.ts index 380cd979..ee5728b8 100644 --- a/src/openfunction/async_server.ts +++ b/src/openfunction/async_server.ts @@ -3,11 +3,7 @@ import {DaprServer} from '@dapr/dapr'; import {OpenFunction} from '../functions'; -import { - OpenFunctionContext, - ContextUtils, - PluginContextRuntime, -} from './function_context'; +import {OpenFunctionContext, ContextUtils} from './function_context'; import {OpenFunctionRuntime} from './function_runtime'; export type AsyncFunctionServer = DaprServer; @@ -27,28 +23,29 @@ export default function ( const ctx = OpenFunctionRuntime.ProxyContext(context); const wrapper = async (data: object) => { - const runtime: PluginContextRuntime = { - context: context, - data: data, - }; - if (context.prePlugins) { - for (let i = 0; i < context.prePlugins!.length; i++) { - const p = context.pluginMap?.get(context.prePlugins![i]); - if (p) { - await p.execPreHook(runtime, context.pluginMap!); - data = runtime.data; + try { + // exec pre hooks + if (context.prePlugins) { + for (const p of context.prePlugins) { + if (p && typeof p === 'object') { + typeof p.execPreHook === 'function' && (await p.execPreHook(ctx)); + } } } - } - await userFunction(ctx, data); - if (context.postPlugins) { - for (let i = 0; i < context.postPlugins!.length; i++) { - const p = context.pluginMap?.get(context.postPlugins![i]); - if (p) { - await p.execPostHook(runtime, context.pluginMap!); - data = runtime.data; + + await userFunction(ctx, data); + + // exec post hooks + if (context.postPlugins) { + for (const p of context.postPlugins) { + if (p && typeof p === 'object') { + typeof p.execPostHook === 'function' && (await p.execPostHook(ctx)); + } } } + } catch (error) { + // if don't catch error process will down + console.error(error); } }; diff --git a/src/openfunction/function_context.ts b/src/openfunction/function_context.ts index 967f9c1d..dbde4535 100644 --- a/src/openfunction/function_context.ts +++ b/src/openfunction/function_context.ts @@ -1,3 +1,5 @@ +import {OpenFunctionRuntime} from './function_runtime'; + /** * The OpenFunction's serving context. * @public @@ -33,15 +35,11 @@ export interface OpenFunctionContext { /** * Optional pre function exec plugins. */ - prePlugins?: Array; + prePlugins?: Array; /** * Optional post function exec plugins. */ - postPlugins?: Array; - /** - * the map of plugin name & class. - */ - pluginMap?: Map; + postPlugins?: Array; } /** @@ -158,60 +156,25 @@ export class ContextUtils { */ export class Plugin { /** - * The plugin init method. - * @method - */ - async init() { - console.log('init'); - } - /** - * The plugin pre hook. - * @method + * pre main function exec. + * @param ctx - The openfunction runtime . */ - async execPreHook(ctx: PluginContextRuntime, plugins: Map) { - console.log(ctx, plugins); + public async execPreHook(ctx?: OpenFunctionRuntime) { + console.log(ctx); } /** - * The plugin post hook. - * @method + * post main function exec. + * @param ctx - The openfunction runtime . */ - async execPostHook(ctx: PluginContextRuntime, plugins: Map) { - console.log(ctx, plugins); + public async execPostHook(ctx?: OpenFunctionRuntime) { + console.log(ctx); } /** - * get plugin filed. - * @method + * get instance filed value. + * @param filedName - the instace filedName + * @returns filed value. */ - get(filedName: string) { + public get(filedName: string) { return filedName; } - /** - * get plugin name. - * @method - */ - pluginName(): string { - return ''; - } - /** - * get plugin version. - * @method - */ - pluginVersion(): string { - return ''; - } -} - -/** - * The OpenFunction's plugin context runtime. - * @public - */ -export interface PluginContextRuntime { - /** - * OpenFunctionRuntime. - */ - context: OpenFunctionContext; - /** - * data. - */ - data: object; } diff --git a/src/plugin.ts b/src/plugin.ts deleted file mode 100644 index 276a71a7..00000000 --- a/src/plugin.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as path from 'path'; -import * as fs from 'fs'; -import {OpenFunctionContext, Plugin} from './openfunction/function_context'; -import {forEach} from 'lodash'; -export async function loadPlugins( - codeLocation: string, - context: OpenFunctionContext -): Promise> { - const pluginMap: Map = new Map(); - if (!context) { - return pluginMap; - } - if (!context.prePlugins && !context.postPlugins) { - return pluginMap; - } - const param = path.resolve(`${codeLocation}/plugins`); - const plugin_files: Array = []; - const files = fs.readdirSync(param); - files.forEach(f => { - if (f.endsWith('.js')) { - plugin_files.push(path.join(param, f)); - } - }); - const set = new Set(); - if (context.prePlugins) { - forEach(context.prePlugins, value => { - set.add(value); - }); - } - if (context.postPlugins) { - forEach(context.postPlugins, value => { - set.add(value); - }); - } - console.log(plugin_files); - plugin_files.forEach(f => { - try { - const pluginModule = require(f); - const p = new pluginModule(); - if ( - p.pluginName && - p.pluginVersion && - p.get && - p.execPreHook && - p.execPostHook && - p.init && - set.has(p.pluginName()) - ) { - pluginMap.set(p.pluginName(), p as Plugin); - } - } catch (error) { - console.error(error); - } - }); - try { - pluginMap.forEach(async item => { - await item.init(); - }); - } catch (error) { - console.error(error); - } - return pluginMap; -} diff --git a/test/data/plugins/errorMissAll.js b/test/data/plugins/errorMissAll.js new file mode 100644 index 00000000..c8fe6621 --- /dev/null +++ b/test/data/plugins/errorMissAll.js @@ -0,0 +1,6 @@ +class ErrorPlugin{ + static Version = "v1" + static Name = "error-miss-all-plugin" +} + +module.exports = ErrorPlugin; diff --git a/test/data/plugins/errorMissGet.js b/test/data/plugins/errorMissGet.js new file mode 100644 index 00000000..1d75045b --- /dev/null +++ b/test/data/plugins/errorMissGet.js @@ -0,0 +1,13 @@ +class ErrorPlugin{ + static Version = "v1" + static Name = "error-miss-get-plugin" + + execPreHook(ctx){ + console.log(`-----------error plugin pre hook-----------`) + } + execPostHook(ctx){ + console.log(`-----------error plugin post hook-----------`) + } +} + +module.exports = ErrorPlugin; diff --git a/test/data/plugins/errorMissName.js b/test/data/plugins/errorMissName.js new file mode 100644 index 00000000..4021adc8 --- /dev/null +++ b/test/data/plugins/errorMissName.js @@ -0,0 +1,22 @@ +class ErrorPlugin{ + static Version = "v1" + // static Name = "error-plugin" + constructor(){ + console.log(`init error plugins`) + } + async execPreHook(ctx){ + console.log(`-----------error plugin pre hook-----------`) + } + execPostHook(ctx){ + console.log(`-----------error plugin post hook-----------`) + } + get(filedName){ + for(let key in this){ + if(key === filedName){ + return this[key] + } + } + } +} + +module.exports = ErrorPlugin; diff --git a/test/data/plugins/errorMissPost.js b/test/data/plugins/errorMissPost.js new file mode 100644 index 00000000..09fa17b6 --- /dev/null +++ b/test/data/plugins/errorMissPost.js @@ -0,0 +1,17 @@ +class ErrorPlugin{ + static Version = "v1" + static Name = "error-miss-post-plugin" + + execPreHook(ctx){ + console.log(`-----------error plugin post hook-----------`) + } + get(filedName){ + for(let key in this){ + if(key === filedName){ + return this[key] + } + } + } +} + +module.exports = ErrorPlugin; diff --git a/test/data/plugins/errorMissPre.js b/test/data/plugins/errorMissPre.js new file mode 100644 index 00000000..5c519d60 --- /dev/null +++ b/test/data/plugins/errorMissPre.js @@ -0,0 +1,17 @@ +class ErrorPlugin{ + static Version = "v1" + static Name = "error-miss-pre-plugin" + + execPostHook(ctx){ + console.log(`-----------error plugin post hook-----------`) + } + get(filedName){ + for(let key in this){ + if(key === filedName){ + return this[key] + } + } + } +} + +module.exports = ErrorPlugin; diff --git a/test/data/plugins/errorMissVersion.js b/test/data/plugins/errorMissVersion.js new file mode 100644 index 00000000..2a01cc33 --- /dev/null +++ b/test/data/plugins/errorMissVersion.js @@ -0,0 +1,22 @@ +class ErrorPlugin{ + // static Version = "v1" + static Name = "error-miss-version-plugin" + constructor(){ + console.log(`init error plugins`) + } + async execPreHook(ctx){ + console.log(`-----------error plugin pre hook-----------`) + } + execPostHook(ctx){ + console.log(`-----------error plugin post hook-----------`) + } + get(filedName){ + for(let key in this){ + if(key === filedName){ + return this[key] + } + } + } +} + +module.exports = ErrorPlugin; diff --git a/test/data/plugins/plugindemo.js b/test/data/plugins/plugindemo.js new file mode 100644 index 00000000..e9fff27b --- /dev/null +++ b/test/data/plugins/plugindemo.js @@ -0,0 +1,33 @@ +const { resolve } = require("path") + +class DemoPlugin{ + static Version = "v1" + static Name = "demo-plugin" + id = '666' + constructor(){ + console.log(`init demo plugins`) + } + sleep(){ + return new Promise(resolve => setTimeout(resolve,3000)); + } + async execPreHook(ctx){ + console.log(`-----------demo plugin pre hook-----------`) + ctx['pre'] = 'pre-exec'; + await this.sleep() + console.log(`-----------sleep 3----------`) + } + execPostHook(ctx){ + console.log(`-----------demo plugin post hook-----------`) + ctx['post'] = 'post-exec'; + console.log(`-----------send post-----------`) + } + get(filedName){ + for(let key in this){ + if(key === filedName){ + return this[key] + } + } + } +} + +module.exports = DemoPlugin; diff --git a/test/integration/async_server.ts b/test/integration/async_server.ts index 23d5a4c2..c46b198c 100644 --- a/test/integration/async_server.ts +++ b/test/integration/async_server.ts @@ -68,10 +68,9 @@ const TEST_CLOUD_EVENT = { describe('OpenFunction - Async - Binding', () => { const APPID = 'async.dapr'; const broker = MQTT.Server(); - + const server = createServer(broker.handle); before(done => { // Start simple plain MQTT server via aedes - const server = createServer(broker.handle); server.listen(1883, () => { // Try to run Dapr sidecar and listen for the async server shell.exec( @@ -90,7 +89,7 @@ describe('OpenFunction - Async - Binding', () => { shell.exec(`dapr stop ${APPID}`, { silent: true, }); - + server.close(); broker.close(done); }); diff --git a/test/integration/test.ts b/test/integration/test.ts new file mode 100644 index 00000000..675e6459 --- /dev/null +++ b/test/integration/test.ts @@ -0,0 +1,204 @@ +/* eslint-disable no-restricted-properties */ +import {deepStrictEqual, ifError, ok} from 'assert'; +import {createServer} from 'net'; + +import {get, isEmpty} from 'lodash'; +import * as shell from 'shelljs'; +import * as MQTT from 'aedes'; + +import {OpenFunctionContext} from '../../src/openfunction/function_context'; +import getAysncServer from '../../src/openfunction/async_server'; +import {getUserPlugins} from '../../src/loader'; +import {FrameworkOptions} from '../../src/options'; +import assert = require('assert'); + +const TEST_CONTEXT: OpenFunctionContext = { + name: 'test-context', + version: '1.0.0', + runtime: 'Async', + port: '8080', + inputs: { + cron: { + uri: 'cron_input', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + mqtt_binding: { + uri: 'default', + componentName: 'binding-mqtt', + componentType: 'bindings.mqtt', + }, + mqtt_sub: { + uri: 'webup', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, + outputs: { + cron: { + uri: 'cron_output', + operation: 'delete', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + localfs: { + uri: 'localstorage', + operation: 'create', + componentName: 'binding-localfs', + componentType: 'bindings.localstorage', + metadata: { + fileName: 'output-file.txt', + }, + }, + mqtt_pub: { + uri: 'webup_pub', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, +}; +const TEST_PLUGIN_OPTIONS: FrameworkOptions = { + port: '', + target: '', + sourceLocation: process.cwd() + '/test/data', + signatureType: 'event', + printHelp: false, + context: { + name: 'test-context-plugin', + version: '1.0.0', + runtime: 'Async', + port: '8080', + inputs: { + cron: { + uri: 'cron_input', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + mqtt_binding: { + uri: 'default', + componentName: 'binding-mqtt', + componentType: 'bindings.mqtt', + }, + mqtt_sub: { + uri: 'webup', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, + outputs: { + cron: { + uri: 'cron_output', + operation: 'delete', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + localfs: { + uri: 'localstorage', + operation: 'create', + componentName: 'binding-localfs', + componentType: 'bindings.localstorage', + metadata: { + fileName: 'output-file.txt', + }, + }, + mqtt_pub: { + uri: 'webup_pub', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, + prePlugins: ['demo-plugin'], + postPlugins: ['demo-plugin'], + }, +}; +const TEST_PAYLOAD = {data: 'hello world'}; +const TEST_CLOUD_EVENT = { + specversion: '1.0', + id: 'test-1234-1234', + type: 'ce.openfunction', + source: 'https://github.com/OpenFunction/functions-framework-nodejs', + traceparent: '00-65088630f09e0a5359677a7429456db7-97f23477fb2bf5ec-01', + data: TEST_PAYLOAD, +}; + +describe('OpenFunction - Async - Binding with plugin', () => { + const APPID = 'async.dapr'; + const broker = MQTT.Server(); + const server = createServer(broker.handle); + + before(done => { + // Start simple plain MQTT server via aedes + server.listen(1883, () => { + // Try to run Dapr sidecar and listen for the async server + shell.exec( + `dapr run -H 3500 -G 50001 -p ${TEST_CONTEXT.port} -d ./test/data/components/async -a ${APPID} --log-level debug`, + { + silent: true, + async: true, + } + ); + done(); + }); + }); + + after(done => { + // Stop dapr sidecar process + shell.exec(`dapr stop ${APPID}`, { + silent: true, + }); + server.close(); + broker.close(done); + }); + + it('mqtt sub w/ pub output with demo plugin', done => { + const app = getAysncServer((ctx, data) => { + if (isEmpty(data)) return; + + const context: any = ctx as any; + assert(context['pre'] === 'pre-exec'); + context['pre'] = 'main-exec'; + + // Assert that user function receives correct data from input binding + deepStrictEqual(data, TEST_PAYLOAD); + console.log(data); + // Then forward received data to output channel + const output = 'mqtt_pub'; + broker.subscribe( + get(TEST_PLUGIN_OPTIONS.context!, `outputs.${output}.uri`), + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (packet, _) => { + const payload = JSON.parse(Buffer.from(packet.payload).toString()); + deepStrictEqual(payload.data, TEST_PAYLOAD); + app + .stop() + .then(() => { + assert(context['pre'] === 'main-exec'); + assert(context['post'] === 'post-exec'); + }) + .finally(done); + }, + () => { + ctx.send(TEST_PAYLOAD, output); + } + ); + }, TEST_PLUGIN_OPTIONS.context!); + + // First, we start the async server + app.start().then(async () => { + await getUserPlugins(TEST_PLUGIN_OPTIONS); + console.log(TEST_PLUGIN_OPTIONS); + // Then, we send a cloudevent format message to server + broker.publish( + { + cmd: 'publish', + topic: TEST_PLUGIN_OPTIONS.context!.inputs!.mqtt_sub.uri!, + payload: JSON.stringify(TEST_CLOUD_EVENT), + qos: 0, + retain: false, + dup: false, + }, + err => ifError(err) + ); + }); + }); +}); diff --git a/test/loader.ts b/test/loader.ts index 07b97d1e..5d190fe0 100644 --- a/test/loader.ts +++ b/test/loader.ts @@ -18,6 +18,7 @@ import * as semver from 'semver'; import * as functions from '../src/functions'; import * as loader from '../src/loader'; import * as FunctionRegistry from '../src/function_registry'; +import {FrameworkOptions} from '../src/options'; describe('loading function', () => { interface TestData { @@ -131,3 +132,207 @@ describe('loading function', () => { assert.strictEqual(loadedFunction?.signatureType, 'cloudevent'); }); }); + +describe('loading plugins', () => { + interface ExceptData { + prePlugins: Array; + postPlugins: Array; + } + const ofn_plugin_name = 'ofn_plugin_name'; + const ofn_plugin_version = 'ofn_plugin_version'; + interface TestData { + options: FrameworkOptions; + except: ExceptData; + } + const testData: TestData[] = [ + { + options: { + port: '8080', + target: 'helloWorld', + sourceLocation: process.cwd() + '/test/data', + signatureType: 'event', + printHelp: false, + context: { + name: 'demo', + version: '', + runtime: 'ASYNC', + prePlugins: ['demo-plugin'], + postPlugins: ['demo-plugin'], + }, + }, + except: { + prePlugins: ['demo-plugin'], + postPlugins: ['demo-plugin'], + }, + }, + { + options: { + port: '8080', + target: 'helloWorld', + sourceLocation: process.cwd() + '/test/data', + signatureType: 'event', + printHelp: false, + context: { + name: 'demo', + version: '', + runtime: 'ASYNC', + prePlugins: ['demo-plugin'], + postPlugins: [], + }, + }, + except: { + prePlugins: ['demo-plugin'], + postPlugins: [], + }, + }, + { + options: { + port: '8080', + target: 'helloWorld', + sourceLocation: process.cwd() + '/test/data', + signatureType: 'event', + printHelp: false, + context: { + name: 'demo', + version: '', + runtime: 'ASYNC', + prePlugins: [], + postPlugins: [], + }, + }, + except: { + prePlugins: [], + postPlugins: [], + }, + }, + { + options: { + port: '8080', + target: 'helloWorld', + sourceLocation: process.cwd() + '/test/data', + signatureType: 'event', + printHelp: false, + context: { + name: 'error', + version: '', + runtime: 'ASYNC', + prePlugins: ['error-plugin'], + postPlugins: ['error-plugin'], + }, + }, + except: { + prePlugins: [], + postPlugins: [], + }, + }, + { + options: { + port: '8080', + target: 'helloWorld', + sourceLocation: process.cwd() + '/test/data', + signatureType: 'event', + printHelp: false, + context: { + name: 'error', + version: '', + runtime: 'ASYNC', + prePlugins: ['error-miss-version-plugin'], + postPlugins: ['error-miss-version-plugin'], + }, + }, + except: { + prePlugins: ['error-miss-version-plugin'], + postPlugins: ['error-miss-version-plugin'], + }, + }, + ]; + + it('load exits plugins', async () => { + for (const test of testData) { + const options = await loader.getUserPlugins(test.options); + const current: ExceptData = { + prePlugins: [], + postPlugins: [], + }; + + options.context!.prePlugins!.forEach(item => { + assert(typeof item === 'object'); + assert(item.get(ofn_plugin_version) === 'v1'); + current.prePlugins.push(item.get(ofn_plugin_name)); + }); + options.context!.postPlugins!.forEach(item => { + assert(typeof item === 'object'); + assert(item.get(ofn_plugin_version) === 'v1'); + current.postPlugins.push(item.get(ofn_plugin_name)); + }); + + assert.deepStrictEqual(current, test.except); + } + }); + const test: TestData = { + options: { + port: '8080', + target: 'helloWorld', + sourceLocation: process.cwd() + '/test/data', + signatureType: 'event', + printHelp: false, + context: { + name: 'error', + version: '', + runtime: 'ASYNC', + prePlugins: [''], + postPlugins: [''], + }, + }, + except: { + prePlugins: [''], + postPlugins: [''], + }, + }; + function copyAndSet(name: string): TestData { + const data: TestData = JSON.parse(JSON.stringify(test)); + data.options.context!.prePlugins![0] = name; + data.options.context!.postPlugins![0] = name; + data.except.postPlugins[0] = name; + data.except.prePlugins[0] = name; + return data; + } + it('user plugin miss prehook', async () => { + const data = copyAndSet('error-error-miss-pre-plugin'); + const options = await loader.getUserPlugins(data.options); + assert.throws(() => { + assert(options.context!.prePlugins!.length === 1); + assert(typeof options.context!.prePlugins![0] === 'object'); + options.context!.prePlugins![0].execPreHook(); + }); + }); + it('user plugin miss posthook', async () => { + const data = copyAndSet('error-error-miss-post-plugin'); + const options = await loader.getUserPlugins(data.options); + assert.throws(() => { + assert(options.context!.prePlugins!.length === 1); + assert(typeof options.context!.prePlugins![0] === 'object'); + options.context!.prePlugins![0].execPostHook(); + }); + }); + it('user plugin miss get', async () => { + const data = copyAndSet('error-error-miss-post-plugin'); + const options = await loader.getUserPlugins(data.options); + assert.throws(() => { + assert(options.context!.prePlugins!.length === 1); + assert(typeof options.context!.prePlugins![0] === 'object'); + options.context!.prePlugins![0].get(''); + }); + }); + it('user plugin miss all', async () => { + const data = copyAndSet('error-error-miss-all-plugin'); + const options = await loader.getUserPlugins(data.options); + assert.throws(() => { + assert(options.context!.prePlugins!.length === 1); + assert(typeof options.context!.prePlugins![0] === 'object'); + options.context!.prePlugins![0].get(''); + options.context!.prePlugins![0].execPostHook(); + options.context!.prePlugins![0].execPreHook(); + }); + }); +}); diff --git a/test/options.ts b/test/options.ts index f7cf727a..ab7a5b84 100644 --- a/test/options.ts +++ b/test/options.ts @@ -125,6 +125,78 @@ describe('parseOptions', () => { printHelp: false, }, }, + { + name: 'respects all env vars', + cliOpts: ['bin/node', '/index.js'], + envVars: { + PORT: '1234', + FUNCTION_TARGET: 'helloWorld', + FUNCTION_SIGNATURE_TYPE: 'cloudevent', + FUNCTION_SOURCE: '/source', + FUNC_CONTEXT: + '{ "name": "foo", "version": "1.0.0", "runtime": "Knative" }', + }, + expectedOptions: { + port: '1234', + target: 'helloWorld', + sourceLocation: resolve('/source'), + signatureType: 'cloudevent', + context: {name: 'foo', version: '1.0.0', runtime: 'Knative'}, + printHelp: false, + }, + }, + { + name: 'respects all env vars with empty plugins', + cliOpts: ['bin/node', '/index.js'], + envVars: { + PORT: '1234', + FUNCTION_TARGET: 'helloWorld', + FUNCTION_SIGNATURE_TYPE: 'cloudevent', + FUNCTION_SOURCE: '/source', + FUNC_CONTEXT: + '{ "name": "foo", "version": "1.0.0", "runtime": "Knative", "prePlugins": [] , "postPlugins": []}', + }, + expectedOptions: { + port: '1234', + target: 'helloWorld', + sourceLocation: resolve('/source'), + signatureType: 'cloudevent', + context: { + name: 'foo', + version: '1.0.0', + runtime: 'Knative', + prePlugins: [], + postPlugins: [], + }, + printHelp: false, + }, + }, + { + name: 'respects all env vars with plugins', + cliOpts: ['bin/node', '/index.js'], + envVars: { + PORT: '1234', + FUNCTION_TARGET: 'helloWorld', + FUNCTION_SIGNATURE_TYPE: 'cloudevent', + FUNCTION_SOURCE: '/source', + FUNC_CONTEXT: + '{ "name": "foo", "version": "1.0.0", "runtime": "Knative", "prePlugins": ["test-plugin"] , "postPlugins": ["test-plugin"]}', + }, + expectedOptions: { + port: '1234', + target: 'helloWorld', + sourceLocation: resolve('/source'), + signatureType: 'cloudevent', + context: { + name: 'foo', + version: '1.0.0', + runtime: 'Knative', + prePlugins: ['test-plugin'], + postPlugins: ['test-plugin'], + }, + printHelp: false, + }, + }, ]; testData.forEach(testCase => { From c8444967c7d97eadeb9a57c050fbfd9de1623a7e Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Sat, 23 Jul 2022 16:51:15 +0800 Subject: [PATCH 05/16] =?UTF-8?q?=F0=9F=8E=A8=20rename=20test=20file=20gi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yad <459647480@qq.com> --- test/integration/{test.ts => async_server_plugin.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/integration/{test.ts => async_server_plugin.ts} (99%) diff --git a/test/integration/test.ts b/test/integration/async_server_plugin.ts similarity index 99% rename from test/integration/test.ts rename to test/integration/async_server_plugin.ts index 675e6459..e21a1d96 100644 --- a/test/integration/test.ts +++ b/test/integration/async_server_plugin.ts @@ -1,5 +1,5 @@ /* eslint-disable no-restricted-properties */ -import {deepStrictEqual, ifError, ok} from 'assert'; +import {deepStrictEqual, ifError} from 'assert'; import {createServer} from 'net'; import {get, isEmpty} from 'lodash'; From af1ac61f9282f09ab0645c441bf88132ec437c8f Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Mon, 25 Jul 2022 19:34:59 +0800 Subject: [PATCH 06/16] =?UTF-8?q?=F0=9F=8E=A8=20improve=20&=20formate=20co?= =?UTF-8?q?de?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yad <459647480@qq.com> --- src/loader.ts | 76 ++++++++++++++++++--------- src/options.ts | 1 + test/data/plugins/errorMissAll.js | 4 +- test/data/plugins/errorMissGet.js | 13 ----- test/data/plugins/errorMissName.js | 16 +++--- test/data/plugins/errorMissPost.js | 17 ------ test/data/plugins/errorMissPre.js | 17 ------ test/data/plugins/errorMissVersion.js | 14 ++--- test/data/plugins/plugindemo.js | 38 +++++++------- test/loader.ts | 16 +++--- 10 files changed, 95 insertions(+), 117 deletions(-) delete mode 100644 test/data/plugins/errorMissGet.js delete mode 100644 test/data/plugins/errorMissPost.js delete mode 100644 test/data/plugins/errorMissPre.js diff --git a/src/loader.ts b/src/loader.ts index e866eb55..01150377 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -23,11 +23,12 @@ import * as semver from 'semver'; import * as readPkgUp from 'read-pkg-up'; import * as fs from 'fs'; import {pathToFileURL} from 'url'; -import {HandlerFunction} from './functions'; +import {HandlerFunction, OpenFunctionRuntime} from './functions'; import {SignatureType} from './types'; import {getRegisteredFunction} from './function_registry'; import {Plugin} from './openfunction/function_context'; import {FrameworkOptions} from './options'; +import {forEach} from 'lodash'; // Dynamic import function required to load user code packaged as an // ES module is only available on Node.js v13.2.0 and up. @@ -95,7 +96,7 @@ export async function getUserFunction( } | null> { try { const functionModulePath = getFunctionModulePath(codeLocation); - console.log(functionModulePath); + if (functionModulePath === null) { console.error('Provided code is not a loadable module.'); return null; @@ -208,17 +209,17 @@ export async function getUserPlugins( ): Promise { // get plugin set const pluginSet: Set = new Set(); - if ( - options.context && - options.context.prePlugins && - options.context.postPlugins - ) { - options.context.prePlugins.forEach(item => { - typeof item === 'string' && pluginSet.add(item); - }); - options.context.postPlugins.forEach(item => { - typeof item === 'string' && pluginSet.add(item); - }); + if (options.context) { + if (options.context.prePlugins) { + forEach(options.context.prePlugins, plugin => { + typeof plugin === 'string' && pluginSet.add(plugin); + }); + } + if (options.context.postPlugins) { + forEach(options.context.postPlugins, plugin => { + typeof plugin === 'string' && pluginSet.add(plugin); + }); + } try { // load plugin js files @@ -250,24 +251,49 @@ export async function getUserPlugins( instance['ofn_plugin_version'] = module.Version ? module.Version : 'v1'; + + //set default method of pre post get + if (!instance.execPreHook) { + instance.execPreHook = (ctx?: OpenFunctionRuntime) => { + console.log(ctx); + }; + } + if (!instance.execPostHook) { + instance.execPostHook = (ctx?: OpenFunctionRuntime) => { + console.log(ctx); + }; + } + if (!instance.get) { + instance.get = (filedName: string) => { + for (const key in instance) { + if (key === filedName) { + return instance[key]; + } + } + }; + } instances.set(arr[k], instance as Plugin); } } const prePlugins: Array = []; const postPlugins: Array = []; - options.context.prePlugins.forEach(item => { - if (typeof item === 'string') { - const instance = instances.get(item); - typeof instance === 'object' && prePlugins.push(instance); - } - }); - options.context.postPlugins.forEach(item => { - if (typeof item === 'string') { - const instance = instances.get(item); - typeof instance === 'object' && postPlugins.push(instance); - } - }); + if (options.context.prePlugins) { + forEach(options.context.prePlugins, plugin => { + if (typeof plugin === 'string') { + const instance = instances.get(plugin); + typeof instance === 'object' && prePlugins.push(instance); + } + }); + } + if (options.context.postPlugins) { + forEach(options.context.postPlugins, plugin => { + if (typeof plugin === 'string') { + const instance = instances.get(plugin); + typeof instance === 'object' && postPlugins.push(instance); + } + }); + } options.context.prePlugins = prePlugins; options.context.postPlugins = postPlugins; diff --git a/src/options.ts b/src/options.ts index 3f4bb726..c3611304 100644 --- a/src/options.ts +++ b/src/options.ts @@ -134,6 +134,7 @@ const FunctionContextOption = new ConfigurableOption( } } ); + export const helpText = `Example usage: functions-framework --target=helloWorld --port=8080 Documentation: diff --git a/test/data/plugins/errorMissAll.js b/test/data/plugins/errorMissAll.js index c8fe6621..413b0248 100644 --- a/test/data/plugins/errorMissAll.js +++ b/test/data/plugins/errorMissAll.js @@ -1,6 +1,6 @@ class ErrorPlugin{ - static Version = "v1" - static Name = "error-miss-all-plugin" + static Version = "v1"; + static Name = "error-miss-all-plugin"; } module.exports = ErrorPlugin; diff --git a/test/data/plugins/errorMissGet.js b/test/data/plugins/errorMissGet.js deleted file mode 100644 index 1d75045b..00000000 --- a/test/data/plugins/errorMissGet.js +++ /dev/null @@ -1,13 +0,0 @@ -class ErrorPlugin{ - static Version = "v1" - static Name = "error-miss-get-plugin" - - execPreHook(ctx){ - console.log(`-----------error plugin pre hook-----------`) - } - execPostHook(ctx){ - console.log(`-----------error plugin post hook-----------`) - } -} - -module.exports = ErrorPlugin; diff --git a/test/data/plugins/errorMissName.js b/test/data/plugins/errorMissName.js index 4021adc8..667d89f1 100644 --- a/test/data/plugins/errorMissName.js +++ b/test/data/plugins/errorMissName.js @@ -1,21 +1,21 @@ class ErrorPlugin{ - static Version = "v1" + static Version = "v1"; // static Name = "error-plugin" constructor(){ - console.log(`init error plugins`) + console.log(`init error plugins`); } async execPreHook(ctx){ - console.log(`-----------error plugin pre hook-----------`) + console.log(`-----------error plugin pre hook-----------`); } execPostHook(ctx){ - console.log(`-----------error plugin post hook-----------`) + console.log(`-----------error plugin post hook-----------`); } get(filedName){ - for(let key in this){ - if(key === filedName){ - return this[key] - } + for(let key in this){ + if(key === filedName){ + return this[key]; } + } } } diff --git a/test/data/plugins/errorMissPost.js b/test/data/plugins/errorMissPost.js deleted file mode 100644 index 09fa17b6..00000000 --- a/test/data/plugins/errorMissPost.js +++ /dev/null @@ -1,17 +0,0 @@ -class ErrorPlugin{ - static Version = "v1" - static Name = "error-miss-post-plugin" - - execPreHook(ctx){ - console.log(`-----------error plugin post hook-----------`) - } - get(filedName){ - for(let key in this){ - if(key === filedName){ - return this[key] - } - } - } -} - -module.exports = ErrorPlugin; diff --git a/test/data/plugins/errorMissPre.js b/test/data/plugins/errorMissPre.js deleted file mode 100644 index 5c519d60..00000000 --- a/test/data/plugins/errorMissPre.js +++ /dev/null @@ -1,17 +0,0 @@ -class ErrorPlugin{ - static Version = "v1" - static Name = "error-miss-pre-plugin" - - execPostHook(ctx){ - console.log(`-----------error plugin post hook-----------`) - } - get(filedName){ - for(let key in this){ - if(key === filedName){ - return this[key] - } - } - } -} - -module.exports = ErrorPlugin; diff --git a/test/data/plugins/errorMissVersion.js b/test/data/plugins/errorMissVersion.js index 2a01cc33..0315b835 100644 --- a/test/data/plugins/errorMissVersion.js +++ b/test/data/plugins/errorMissVersion.js @@ -2,20 +2,20 @@ class ErrorPlugin{ // static Version = "v1" static Name = "error-miss-version-plugin" constructor(){ - console.log(`init error plugins`) + console.log(`init error plugins`); } async execPreHook(ctx){ - console.log(`-----------error plugin pre hook-----------`) + console.log(`-----------error plugin pre hook-----------`); } execPostHook(ctx){ - console.log(`-----------error plugin post hook-----------`) + console.log(`-----------error plugin post hook-----------`); } get(filedName){ - for(let key in this){ - if(key === filedName){ - return this[key] - } + for(let key in this){ + if(key === filedName){ + return this[key]; } + } } } diff --git a/test/data/plugins/plugindemo.js b/test/data/plugins/plugindemo.js index e9fff27b..8c8a6dae 100644 --- a/test/data/plugins/plugindemo.js +++ b/test/data/plugins/plugindemo.js @@ -1,32 +1,30 @@ -const { resolve } = require("path") - +function sleep(){ + return new Promise(resolve => setTimeout(resolve,3000)); +} class DemoPlugin{ - static Version = "v1" - static Name = "demo-plugin" - id = '666' + static Version = "v1"; + static Name = "demo-plugin"; + id = '666'; constructor(){ - console.log(`init demo plugins`) - } - sleep(){ - return new Promise(resolve => setTimeout(resolve,3000)); + console.log(`init demo plugins`); } async execPreHook(ctx){ - console.log(`-----------demo plugin pre hook-----------`) - ctx['pre'] = 'pre-exec'; - await this.sleep() - console.log(`-----------sleep 3----------`) + console.log(`-----------demo plugin pre hook-----------`); + ctx['pre'] = 'pre-exec'; + await sleep(); + console.log(`-----------sleep 3----------`) } execPostHook(ctx){ - console.log(`-----------demo plugin post hook-----------`) - ctx['post'] = 'post-exec'; - console.log(`-----------send post-----------`) + console.log(`-----------demo plugin post hook-----------`); + ctx['post'] = 'post-exec'; + console.log(`-----------send post-----------`); } get(filedName){ - for(let key in this){ - if(key === filedName){ - return this[key] - } + for(let key in this){ + if(key === filedName){ + return this[key]; } + } } } diff --git a/test/loader.ts b/test/loader.ts index 5d190fe0..21c08b44 100644 --- a/test/loader.ts +++ b/test/loader.ts @@ -325,14 +325,14 @@ describe('loading plugins', () => { }); }); it('user plugin miss all', async () => { - const data = copyAndSet('error-error-miss-all-plugin'); + const data = copyAndSet('error-miss-all-plugin'); const options = await loader.getUserPlugins(data.options); - assert.throws(() => { - assert(options.context!.prePlugins!.length === 1); - assert(typeof options.context!.prePlugins![0] === 'object'); - options.context!.prePlugins![0].get(''); - options.context!.prePlugins![0].execPostHook(); - options.context!.prePlugins![0].execPreHook(); - }); + assert(typeof options.context!.prePlugins![0] === 'object'); + assert( + options.context!.prePlugins![0].get('ofn_plugin_name') === + 'error-miss-all-plugin' + ); + assert(options.context!.prePlugins![0].execPreHook); + assert(options.context!.prePlugins![0].execPostHook); }); }); From 4bb5ad6549f6c0074145a7de608982725885c2fd Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Mon, 25 Jul 2022 19:51:58 +0800 Subject: [PATCH 07/16] =?UTF-8?q?=F0=9F=8E=A8=20format=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yad <459647480@qq.com> --- src/loader.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/loader.ts b/src/loader.ts index 01150377..a865d4e2 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -248,9 +248,7 @@ export async function getUserPlugins( if (module) { const instance = new module(); instance['ofn_plugin_name'] = module.Name; - instance['ofn_plugin_version'] = module.Version - ? module.Version - : 'v1'; + instance['ofn_plugin_version'] = module.Version || 'v1'; //set default method of pre post get if (!instance.execPreHook) { From 0d5ceb3ac9ba16f6458f8488eb6e6c993a2b0aa3 Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Mon, 25 Jul 2022 20:50:48 +0800 Subject: [PATCH 08/16] =?UTF-8?q?=F0=9F=8E=A8=20extract=20common=20test=20?= =?UTF-8?q?data=20into=20a=20separate=20file=20for=20reuse=20&=20=20use=20?= =?UTF-8?q?Record=20replace=20Map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yad <459647480@qq.com> --- src/loader.ts | 16 ++-- test/data/test_data/async_test_data.ts | 114 +++++++++++++++++++++++ test/integration/async_server.ts | 61 +------------ test/integration/async_server_plugin.ts | 116 ++---------------------- test/loader.ts | 30 +----- 5 files changed, 137 insertions(+), 200 deletions(-) create mode 100644 test/data/test_data/async_test_data.ts diff --git a/src/loader.ts b/src/loader.ts index a865d4e2..9801cdef 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -222,8 +222,9 @@ export async function getUserPlugins( } try { + type Instance = Record; // load plugin js files - const instances: Map = new Map(); + const instances: Instance = {}; const param = path.resolve(`${options.sourceLocation}/plugins`); const plugin_files: Array = []; const files = fs.readdirSync(param); @@ -233,18 +234,19 @@ export async function getUserPlugins( } // find plugins class - const tempMap: Map = new Map(); + type PluginClass = Record; + const tempMap: PluginClass = {}; for (const k in plugin_files) { const jsMoulde = require(plugin_files[k]); if (jsMoulde && jsMoulde.Name) { - tempMap.set(jsMoulde.Name, jsMoulde); + tempMap[jsMoulde.Name] = jsMoulde; } } // instance plugin dynamic set ofn_plugin_name const arr = Array.from(pluginSet.values()); for (const k in arr) { - const module = tempMap.get(arr[k]); + const module = tempMap[arr[k]]; if (module) { const instance = new module(); instance['ofn_plugin_name'] = module.Name; @@ -270,7 +272,7 @@ export async function getUserPlugins( } }; } - instances.set(arr[k], instance as Plugin); + instances[arr[k]] = instance as Plugin; } } @@ -279,7 +281,7 @@ export async function getUserPlugins( if (options.context.prePlugins) { forEach(options.context.prePlugins, plugin => { if (typeof plugin === 'string') { - const instance = instances.get(plugin); + const instance = instances[plugin]; typeof instance === 'object' && prePlugins.push(instance); } }); @@ -287,7 +289,7 @@ export async function getUserPlugins( if (options.context.postPlugins) { forEach(options.context.postPlugins, plugin => { if (typeof plugin === 'string') { - const instance = instances.get(plugin); + const instance = instances[plugin]; typeof instance === 'object' && postPlugins.push(instance); } }); diff --git a/test/data/test_data/async_test_data.ts b/test/data/test_data/async_test_data.ts new file mode 100644 index 00000000..0eb8ed49 --- /dev/null +++ b/test/data/test_data/async_test_data.ts @@ -0,0 +1,114 @@ +import {OpenFunctionContext} from '../../../src'; +import {FrameworkOptions} from '../../../src/options'; + +export const TEST_CONTEXT: OpenFunctionContext = { + name: 'test-context', + version: '1.0.0', + runtime: 'Async', + port: '8080', + inputs: { + cron: { + uri: 'cron_input', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + mqtt_binding: { + uri: 'default', + componentName: 'binding-mqtt', + componentType: 'bindings.mqtt', + }, + mqtt_sub: { + uri: 'webup', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, + outputs: { + cron: { + uri: 'cron_output', + operation: 'delete', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + localfs: { + uri: 'localstorage', + operation: 'create', + componentName: 'binding-localfs', + componentType: 'bindings.localstorage', + metadata: { + fileName: 'output-file.txt', + }, + }, + mqtt_pub: { + uri: 'webup_pub', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, +}; + +export const TEST_PAYLOAD = {data: 'hello world'}; + +export const TEST_CLOUD_EVENT = { + specversion: '1.0', + id: 'test-1234-1234', + type: 'ce.openfunction', + source: 'https://github.com/OpenFunction/functions-framework-nodejs', + traceparent: '00-65088630f09e0a5359677a7429456db7-97f23477fb2bf5ec-01', + data: TEST_PAYLOAD, +}; + +export const TEST_PLUGIN_OPTIONS: FrameworkOptions = { + port: '', + target: '', + sourceLocation: process.cwd() + '/test/data', + signatureType: 'event', + printHelp: false, + context: { + name: 'test-context-plugin', + version: '1.0.0', + runtime: 'Async', + port: '8080', + inputs: { + cron: { + uri: 'cron_input', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + mqtt_binding: { + uri: 'default', + componentName: 'binding-mqtt', + componentType: 'bindings.mqtt', + }, + mqtt_sub: { + uri: 'webup', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, + outputs: { + cron: { + uri: 'cron_output', + operation: 'delete', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + localfs: { + uri: 'localstorage', + operation: 'create', + componentName: 'binding-localfs', + componentType: 'bindings.localstorage', + metadata: { + fileName: 'output-file.txt', + }, + }, + mqtt_pub: { + uri: 'webup_pub', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, + prePlugins: ['demo-plugin'], + postPlugins: ['demo-plugin'], + }, +}; diff --git a/test/integration/async_server.ts b/test/integration/async_server.ts index c46b198c..fe215b15 100644 --- a/test/integration/async_server.ts +++ b/test/integration/async_server.ts @@ -6,64 +6,13 @@ import {get, isEmpty} from 'lodash'; import * as shell from 'shelljs'; import * as MQTT from 'aedes'; -import {OpenFunctionContext} from '../../src/openfunction/function_context'; import getAysncServer from '../../src/openfunction/async_server'; -const TEST_CONTEXT: OpenFunctionContext = { - name: 'test-context', - version: '1.0.0', - runtime: 'Async', - port: '8080', - inputs: { - cron: { - uri: 'cron_input', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - mqtt_binding: { - uri: 'default', - componentName: 'binding-mqtt', - componentType: 'bindings.mqtt', - }, - mqtt_sub: { - uri: 'webup', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, - outputs: { - cron: { - uri: 'cron_output', - operation: 'delete', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - localfs: { - uri: 'localstorage', - operation: 'create', - componentName: 'binding-localfs', - componentType: 'bindings.localstorage', - metadata: { - fileName: 'output-file.txt', - }, - }, - mqtt_pub: { - uri: 'webup_pub', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, -}; - -const TEST_PAYLOAD = {data: 'hello world'}; -const TEST_CLOUD_EVENT = { - specversion: '1.0', - id: 'test-1234-1234', - type: 'ce.openfunction', - source: 'https://github.com/OpenFunction/functions-framework-nodejs', - traceparent: '00-65088630f09e0a5359677a7429456db7-97f23477fb2bf5ec-01', - data: TEST_PAYLOAD, -}; +import { + TEST_CLOUD_EVENT, + TEST_CONTEXT, + TEST_PAYLOAD, +} from '../data/test_data/async_test_data'; describe('OpenFunction - Async - Binding', () => { const APPID = 'async.dapr'; diff --git a/test/integration/async_server_plugin.ts b/test/integration/async_server_plugin.ts index e21a1d96..0f3ca33b 100644 --- a/test/integration/async_server_plugin.ts +++ b/test/integration/async_server_plugin.ts @@ -6,120 +6,16 @@ import {get, isEmpty} from 'lodash'; import * as shell from 'shelljs'; import * as MQTT from 'aedes'; -import {OpenFunctionContext} from '../../src/openfunction/function_context'; import getAysncServer from '../../src/openfunction/async_server'; import {getUserPlugins} from '../../src/loader'; -import {FrameworkOptions} from '../../src/options'; import assert = require('assert'); -const TEST_CONTEXT: OpenFunctionContext = { - name: 'test-context', - version: '1.0.0', - runtime: 'Async', - port: '8080', - inputs: { - cron: { - uri: 'cron_input', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - mqtt_binding: { - uri: 'default', - componentName: 'binding-mqtt', - componentType: 'bindings.mqtt', - }, - mqtt_sub: { - uri: 'webup', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, - outputs: { - cron: { - uri: 'cron_output', - operation: 'delete', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - localfs: { - uri: 'localstorage', - operation: 'create', - componentName: 'binding-localfs', - componentType: 'bindings.localstorage', - metadata: { - fileName: 'output-file.txt', - }, - }, - mqtt_pub: { - uri: 'webup_pub', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, -}; -const TEST_PLUGIN_OPTIONS: FrameworkOptions = { - port: '', - target: '', - sourceLocation: process.cwd() + '/test/data', - signatureType: 'event', - printHelp: false, - context: { - name: 'test-context-plugin', - version: '1.0.0', - runtime: 'Async', - port: '8080', - inputs: { - cron: { - uri: 'cron_input', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - mqtt_binding: { - uri: 'default', - componentName: 'binding-mqtt', - componentType: 'bindings.mqtt', - }, - mqtt_sub: { - uri: 'webup', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, - outputs: { - cron: { - uri: 'cron_output', - operation: 'delete', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - localfs: { - uri: 'localstorage', - operation: 'create', - componentName: 'binding-localfs', - componentType: 'bindings.localstorage', - metadata: { - fileName: 'output-file.txt', - }, - }, - mqtt_pub: { - uri: 'webup_pub', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, - prePlugins: ['demo-plugin'], - postPlugins: ['demo-plugin'], - }, -}; -const TEST_PAYLOAD = {data: 'hello world'}; -const TEST_CLOUD_EVENT = { - specversion: '1.0', - id: 'test-1234-1234', - type: 'ce.openfunction', - source: 'https://github.com/OpenFunction/functions-framework-nodejs', - traceparent: '00-65088630f09e0a5359677a7429456db7-97f23477fb2bf5ec-01', - data: TEST_PAYLOAD, -}; +import { + TEST_CLOUD_EVENT, + TEST_CONTEXT, + TEST_PAYLOAD, + TEST_PLUGIN_OPTIONS, +} from '../data/test_data/async_test_data'; describe('OpenFunction - Async - Binding with plugin', () => { const APPID = 'async.dapr'; diff --git a/test/loader.ts b/test/loader.ts index 21c08b44..b792ada8 100644 --- a/test/loader.ts +++ b/test/loader.ts @@ -269,6 +269,7 @@ describe('loading plugins', () => { assert.deepStrictEqual(current, test.except); } }); + const test: TestData = { options: { port: '8080', @@ -289,6 +290,7 @@ describe('loading plugins', () => { postPlugins: [''], }, }; + function copyAndSet(name: string): TestData { const data: TestData = JSON.parse(JSON.stringify(test)); data.options.context!.prePlugins![0] = name; @@ -297,33 +299,7 @@ describe('loading plugins', () => { data.except.prePlugins[0] = name; return data; } - it('user plugin miss prehook', async () => { - const data = copyAndSet('error-error-miss-pre-plugin'); - const options = await loader.getUserPlugins(data.options); - assert.throws(() => { - assert(options.context!.prePlugins!.length === 1); - assert(typeof options.context!.prePlugins![0] === 'object'); - options.context!.prePlugins![0].execPreHook(); - }); - }); - it('user plugin miss posthook', async () => { - const data = copyAndSet('error-error-miss-post-plugin'); - const options = await loader.getUserPlugins(data.options); - assert.throws(() => { - assert(options.context!.prePlugins!.length === 1); - assert(typeof options.context!.prePlugins![0] === 'object'); - options.context!.prePlugins![0].execPostHook(); - }); - }); - it('user plugin miss get', async () => { - const data = copyAndSet('error-error-miss-post-plugin'); - const options = await loader.getUserPlugins(data.options); - assert.throws(() => { - assert(options.context!.prePlugins!.length === 1); - assert(typeof options.context!.prePlugins![0] === 'object'); - options.context!.prePlugins![0].get(''); - }); - }); + it('user plugin miss all', async () => { const data = copyAndSet('error-miss-all-plugin'); const options = await loader.getUserPlugins(data.options); From 184c2a9cf894bd4f68298cf6cb0b2de0cc61a066 Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Mon, 25 Jul 2022 20:58:29 +0800 Subject: [PATCH 09/16] =?UTF-8?q?=F0=9F=8E=A8=20delete=20test=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yad <459647480@qq.com> --- test/data/test_data/async_test_data.ts | 114 ----------------------- test/integration/async_server.ts | 61 ++++++++++++- test/integration/async_server_plugin.ts | 116 ++++++++++++++++++++++-- 3 files changed, 166 insertions(+), 125 deletions(-) delete mode 100644 test/data/test_data/async_test_data.ts diff --git a/test/data/test_data/async_test_data.ts b/test/data/test_data/async_test_data.ts deleted file mode 100644 index 0eb8ed49..00000000 --- a/test/data/test_data/async_test_data.ts +++ /dev/null @@ -1,114 +0,0 @@ -import {OpenFunctionContext} from '../../../src'; -import {FrameworkOptions} from '../../../src/options'; - -export const TEST_CONTEXT: OpenFunctionContext = { - name: 'test-context', - version: '1.0.0', - runtime: 'Async', - port: '8080', - inputs: { - cron: { - uri: 'cron_input', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - mqtt_binding: { - uri: 'default', - componentName: 'binding-mqtt', - componentType: 'bindings.mqtt', - }, - mqtt_sub: { - uri: 'webup', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, - outputs: { - cron: { - uri: 'cron_output', - operation: 'delete', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - localfs: { - uri: 'localstorage', - operation: 'create', - componentName: 'binding-localfs', - componentType: 'bindings.localstorage', - metadata: { - fileName: 'output-file.txt', - }, - }, - mqtt_pub: { - uri: 'webup_pub', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, -}; - -export const TEST_PAYLOAD = {data: 'hello world'}; - -export const TEST_CLOUD_EVENT = { - specversion: '1.0', - id: 'test-1234-1234', - type: 'ce.openfunction', - source: 'https://github.com/OpenFunction/functions-framework-nodejs', - traceparent: '00-65088630f09e0a5359677a7429456db7-97f23477fb2bf5ec-01', - data: TEST_PAYLOAD, -}; - -export const TEST_PLUGIN_OPTIONS: FrameworkOptions = { - port: '', - target: '', - sourceLocation: process.cwd() + '/test/data', - signatureType: 'event', - printHelp: false, - context: { - name: 'test-context-plugin', - version: '1.0.0', - runtime: 'Async', - port: '8080', - inputs: { - cron: { - uri: 'cron_input', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - mqtt_binding: { - uri: 'default', - componentName: 'binding-mqtt', - componentType: 'bindings.mqtt', - }, - mqtt_sub: { - uri: 'webup', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, - outputs: { - cron: { - uri: 'cron_output', - operation: 'delete', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - localfs: { - uri: 'localstorage', - operation: 'create', - componentName: 'binding-localfs', - componentType: 'bindings.localstorage', - metadata: { - fileName: 'output-file.txt', - }, - }, - mqtt_pub: { - uri: 'webup_pub', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, - prePlugins: ['demo-plugin'], - postPlugins: ['demo-plugin'], - }, -}; diff --git a/test/integration/async_server.ts b/test/integration/async_server.ts index fe215b15..c46b198c 100644 --- a/test/integration/async_server.ts +++ b/test/integration/async_server.ts @@ -6,13 +6,64 @@ import {get, isEmpty} from 'lodash'; import * as shell from 'shelljs'; import * as MQTT from 'aedes'; +import {OpenFunctionContext} from '../../src/openfunction/function_context'; import getAysncServer from '../../src/openfunction/async_server'; -import { - TEST_CLOUD_EVENT, - TEST_CONTEXT, - TEST_PAYLOAD, -} from '../data/test_data/async_test_data'; +const TEST_CONTEXT: OpenFunctionContext = { + name: 'test-context', + version: '1.0.0', + runtime: 'Async', + port: '8080', + inputs: { + cron: { + uri: 'cron_input', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + mqtt_binding: { + uri: 'default', + componentName: 'binding-mqtt', + componentType: 'bindings.mqtt', + }, + mqtt_sub: { + uri: 'webup', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, + outputs: { + cron: { + uri: 'cron_output', + operation: 'delete', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + localfs: { + uri: 'localstorage', + operation: 'create', + componentName: 'binding-localfs', + componentType: 'bindings.localstorage', + metadata: { + fileName: 'output-file.txt', + }, + }, + mqtt_pub: { + uri: 'webup_pub', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, +}; + +const TEST_PAYLOAD = {data: 'hello world'}; +const TEST_CLOUD_EVENT = { + specversion: '1.0', + id: 'test-1234-1234', + type: 'ce.openfunction', + source: 'https://github.com/OpenFunction/functions-framework-nodejs', + traceparent: '00-65088630f09e0a5359677a7429456db7-97f23477fb2bf5ec-01', + data: TEST_PAYLOAD, +}; describe('OpenFunction - Async - Binding', () => { const APPID = 'async.dapr'; diff --git a/test/integration/async_server_plugin.ts b/test/integration/async_server_plugin.ts index 0f3ca33b..e21a1d96 100644 --- a/test/integration/async_server_plugin.ts +++ b/test/integration/async_server_plugin.ts @@ -6,16 +6,120 @@ import {get, isEmpty} from 'lodash'; import * as shell from 'shelljs'; import * as MQTT from 'aedes'; +import {OpenFunctionContext} from '../../src/openfunction/function_context'; import getAysncServer from '../../src/openfunction/async_server'; import {getUserPlugins} from '../../src/loader'; +import {FrameworkOptions} from '../../src/options'; import assert = require('assert'); -import { - TEST_CLOUD_EVENT, - TEST_CONTEXT, - TEST_PAYLOAD, - TEST_PLUGIN_OPTIONS, -} from '../data/test_data/async_test_data'; +const TEST_CONTEXT: OpenFunctionContext = { + name: 'test-context', + version: '1.0.0', + runtime: 'Async', + port: '8080', + inputs: { + cron: { + uri: 'cron_input', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + mqtt_binding: { + uri: 'default', + componentName: 'binding-mqtt', + componentType: 'bindings.mqtt', + }, + mqtt_sub: { + uri: 'webup', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, + outputs: { + cron: { + uri: 'cron_output', + operation: 'delete', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + localfs: { + uri: 'localstorage', + operation: 'create', + componentName: 'binding-localfs', + componentType: 'bindings.localstorage', + metadata: { + fileName: 'output-file.txt', + }, + }, + mqtt_pub: { + uri: 'webup_pub', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, +}; +const TEST_PLUGIN_OPTIONS: FrameworkOptions = { + port: '', + target: '', + sourceLocation: process.cwd() + '/test/data', + signatureType: 'event', + printHelp: false, + context: { + name: 'test-context-plugin', + version: '1.0.0', + runtime: 'Async', + port: '8080', + inputs: { + cron: { + uri: 'cron_input', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + mqtt_binding: { + uri: 'default', + componentName: 'binding-mqtt', + componentType: 'bindings.mqtt', + }, + mqtt_sub: { + uri: 'webup', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, + outputs: { + cron: { + uri: 'cron_output', + operation: 'delete', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + localfs: { + uri: 'localstorage', + operation: 'create', + componentName: 'binding-localfs', + componentType: 'bindings.localstorage', + metadata: { + fileName: 'output-file.txt', + }, + }, + mqtt_pub: { + uri: 'webup_pub', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, + prePlugins: ['demo-plugin'], + postPlugins: ['demo-plugin'], + }, +}; +const TEST_PAYLOAD = {data: 'hello world'}; +const TEST_CLOUD_EVENT = { + specversion: '1.0', + id: 'test-1234-1234', + type: 'ce.openfunction', + source: 'https://github.com/OpenFunction/functions-framework-nodejs', + traceparent: '00-65088630f09e0a5359677a7429456db7-97f23477fb2bf5ec-01', + data: TEST_PAYLOAD, +}; describe('OpenFunction - Async - Binding with plugin', () => { const APPID = 'async.dapr'; From eae45d877348d173bdcc3437f1d96ff5fcfb26f6 Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Wed, 27 Jul 2022 19:33:34 +0800 Subject: [PATCH 10/16] =?UTF-8?q?=F0=9F=8E=A8=20test=20data=20in=20common?= =?UTF-8?q?=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yad <459647480@qq.com> --- test/data/test_data/async_plugin.ts | 111 ++++++++++++++++++++++ test/integration/async_server.ts | 62 ++---------- test/integration/async_server_plugin.ts | 119 ++---------------------- 3 files changed, 125 insertions(+), 167 deletions(-) create mode 100644 test/data/test_data/async_plugin.ts diff --git a/test/data/test_data/async_plugin.ts b/test/data/test_data/async_plugin.ts new file mode 100644 index 00000000..c281a314 --- /dev/null +++ b/test/data/test_data/async_plugin.ts @@ -0,0 +1,111 @@ +import {OpenFunctionContext} from '../../../src/openfunction/function_context'; +import {FrameworkOptions} from '../../../src/options'; + +export const TEST_CONTEXT: OpenFunctionContext = { + name: 'test-context', + version: '1.0.0', + runtime: 'Async', + port: '8080', + inputs: { + cron: { + uri: 'cron_input', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + mqtt_binding: { + uri: 'default', + componentName: 'binding-mqtt', + componentType: 'bindings.mqtt', + }, + mqtt_sub: { + uri: 'webup', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, + outputs: { + cron: { + uri: 'cron_output', + operation: 'delete', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + localfs: { + uri: 'localstorage', + operation: 'create', + componentName: 'binding-localfs', + componentType: 'bindings.localstorage', + metadata: { + fileName: 'output-file.txt', + }, + }, + mqtt_pub: { + uri: 'webup_pub', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, +}; +export const TEST_PLUGIN_OPTIONS: FrameworkOptions = { + port: '', + target: '', + sourceLocation: process.cwd() + '/test/data', + signatureType: 'event', + printHelp: false, + context: { + name: 'test-context-plugin', + version: '1.0.0', + runtime: 'Async', + port: '8080', + inputs: { + cron: { + uri: 'cron_input', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + mqtt_binding: { + uri: 'default', + componentName: 'binding-mqtt', + componentType: 'bindings.mqtt', + }, + mqtt_sub: { + uri: 'webup', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, + outputs: { + cron: { + uri: 'cron_output', + operation: 'delete', + componentName: 'binding-cron', + componentType: 'bindings.cron', + }, + localfs: { + uri: 'localstorage', + operation: 'create', + componentName: 'binding-localfs', + componentType: 'bindings.localstorage', + metadata: { + fileName: 'output-file.txt', + }, + }, + mqtt_pub: { + uri: 'webup_pub', + componentName: 'pubsub-mqtt', + componentType: 'pubsub.mqtt', + }, + }, + prePlugins: ['demo-plugin'], + postPlugins: ['demo-plugin'], + }, +}; +export const TEST_PAYLOAD = {data: 'hello world'}; +export const TEST_CLOUD_EVENT = { + specversion: '1.0', + id: 'test-1234-1234', + type: 'ce.openfunction', + source: 'https://github.com/OpenFunction/functions-framework-nodejs', + traceparent: '00-65088630f09e0a5359677a7429456db7-97f23477fb2bf5ec-01', + data: TEST_PAYLOAD, +}; diff --git a/test/integration/async_server.ts b/test/integration/async_server.ts index c46b198c..ada0becf 100644 --- a/test/integration/async_server.ts +++ b/test/integration/async_server.ts @@ -6,64 +6,14 @@ import {get, isEmpty} from 'lodash'; import * as shell from 'shelljs'; import * as MQTT from 'aedes'; -import {OpenFunctionContext} from '../../src/openfunction/function_context'; +// import {OpenFunctionContext} from '../../src/openfunction/function_context'; import getAysncServer from '../../src/openfunction/async_server'; -const TEST_CONTEXT: OpenFunctionContext = { - name: 'test-context', - version: '1.0.0', - runtime: 'Async', - port: '8080', - inputs: { - cron: { - uri: 'cron_input', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - mqtt_binding: { - uri: 'default', - componentName: 'binding-mqtt', - componentType: 'bindings.mqtt', - }, - mqtt_sub: { - uri: 'webup', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, - outputs: { - cron: { - uri: 'cron_output', - operation: 'delete', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - localfs: { - uri: 'localstorage', - operation: 'create', - componentName: 'binding-localfs', - componentType: 'bindings.localstorage', - metadata: { - fileName: 'output-file.txt', - }, - }, - mqtt_pub: { - uri: 'webup_pub', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, -}; - -const TEST_PAYLOAD = {data: 'hello world'}; -const TEST_CLOUD_EVENT = { - specversion: '1.0', - id: 'test-1234-1234', - type: 'ce.openfunction', - source: 'https://github.com/OpenFunction/functions-framework-nodejs', - traceparent: '00-65088630f09e0a5359677a7429456db7-97f23477fb2bf5ec-01', - data: TEST_PAYLOAD, -}; +import { + TEST_CLOUD_EVENT, + TEST_CONTEXT, + TEST_PAYLOAD, +} from '../data/test_data/async_plugin'; describe('OpenFunction - Async - Binding', () => { const APPID = 'async.dapr'; diff --git a/test/integration/async_server_plugin.ts b/test/integration/async_server_plugin.ts index e21a1d96..b8c89b6f 100644 --- a/test/integration/async_server_plugin.ts +++ b/test/integration/async_server_plugin.ts @@ -6,120 +6,17 @@ import {get, isEmpty} from 'lodash'; import * as shell from 'shelljs'; import * as MQTT from 'aedes'; -import {OpenFunctionContext} from '../../src/openfunction/function_context'; +// import {OpenFunctionContext} from '../../src/openfunction/function_context'; import getAysncServer from '../../src/openfunction/async_server'; import {getUserPlugins} from '../../src/loader'; -import {FrameworkOptions} from '../../src/options'; +// import {FrameworkOptions} from '../../src/options'; import assert = require('assert'); - -const TEST_CONTEXT: OpenFunctionContext = { - name: 'test-context', - version: '1.0.0', - runtime: 'Async', - port: '8080', - inputs: { - cron: { - uri: 'cron_input', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - mqtt_binding: { - uri: 'default', - componentName: 'binding-mqtt', - componentType: 'bindings.mqtt', - }, - mqtt_sub: { - uri: 'webup', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, - outputs: { - cron: { - uri: 'cron_output', - operation: 'delete', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - localfs: { - uri: 'localstorage', - operation: 'create', - componentName: 'binding-localfs', - componentType: 'bindings.localstorage', - metadata: { - fileName: 'output-file.txt', - }, - }, - mqtt_pub: { - uri: 'webup_pub', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, -}; -const TEST_PLUGIN_OPTIONS: FrameworkOptions = { - port: '', - target: '', - sourceLocation: process.cwd() + '/test/data', - signatureType: 'event', - printHelp: false, - context: { - name: 'test-context-plugin', - version: '1.0.0', - runtime: 'Async', - port: '8080', - inputs: { - cron: { - uri: 'cron_input', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - mqtt_binding: { - uri: 'default', - componentName: 'binding-mqtt', - componentType: 'bindings.mqtt', - }, - mqtt_sub: { - uri: 'webup', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, - outputs: { - cron: { - uri: 'cron_output', - operation: 'delete', - componentName: 'binding-cron', - componentType: 'bindings.cron', - }, - localfs: { - uri: 'localstorage', - operation: 'create', - componentName: 'binding-localfs', - componentType: 'bindings.localstorage', - metadata: { - fileName: 'output-file.txt', - }, - }, - mqtt_pub: { - uri: 'webup_pub', - componentName: 'pubsub-mqtt', - componentType: 'pubsub.mqtt', - }, - }, - prePlugins: ['demo-plugin'], - postPlugins: ['demo-plugin'], - }, -}; -const TEST_PAYLOAD = {data: 'hello world'}; -const TEST_CLOUD_EVENT = { - specversion: '1.0', - id: 'test-1234-1234', - type: 'ce.openfunction', - source: 'https://github.com/OpenFunction/functions-framework-nodejs', - traceparent: '00-65088630f09e0a5359677a7429456db7-97f23477fb2bf5ec-01', - data: TEST_PAYLOAD, -}; +import { + TEST_CLOUD_EVENT, + TEST_CONTEXT, + TEST_PAYLOAD, + TEST_PLUGIN_OPTIONS, +} from '../data/test_data/async_plugin'; describe('OpenFunction - Async - Binding with plugin', () => { const APPID = 'async.dapr'; From a95dad26f4f13d70a624ad50a52f2b9fe0b3f0c0 Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Wed, 27 Jul 2022 19:35:26 +0800 Subject: [PATCH 11/16] =?UTF-8?q?=F0=9F=8E=A8=20=20remove=20annotation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yad <459647480@qq.com> --- test/integration/async_server.ts | 1 - test/integration/async_server_plugin.ts | 2 -- 2 files changed, 3 deletions(-) diff --git a/test/integration/async_server.ts b/test/integration/async_server.ts index ada0becf..e9cddd53 100644 --- a/test/integration/async_server.ts +++ b/test/integration/async_server.ts @@ -6,7 +6,6 @@ import {get, isEmpty} from 'lodash'; import * as shell from 'shelljs'; import * as MQTT from 'aedes'; -// import {OpenFunctionContext} from '../../src/openfunction/function_context'; import getAysncServer from '../../src/openfunction/async_server'; import { diff --git a/test/integration/async_server_plugin.ts b/test/integration/async_server_plugin.ts index b8c89b6f..c7e228ea 100644 --- a/test/integration/async_server_plugin.ts +++ b/test/integration/async_server_plugin.ts @@ -6,10 +6,8 @@ import {get, isEmpty} from 'lodash'; import * as shell from 'shelljs'; import * as MQTT from 'aedes'; -// import {OpenFunctionContext} from '../../src/openfunction/function_context'; import getAysncServer from '../../src/openfunction/async_server'; import {getUserPlugins} from '../../src/loader'; -// import {FrameworkOptions} from '../../src/options'; import assert = require('assert'); import { TEST_CLOUD_EVENT, From a2f2db7a0b439d80db23691c95af5a59dd646757 Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Wed, 3 Aug 2022 10:55:37 +0800 Subject: [PATCH 12/16] =?UTF-8?q?=F0=9F=8E=A8=20improve=20the=20codegit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yad <459647480@qq.com> --- src/loader.ts | 81 +++++++++++++++++++-------- src/main.ts | 7 +-- src/openfunction/async_server.ts | 38 ++++++------- src/openfunction/function_context.ts | 16 +++++- test/data/plugins/errorMissAll.js | 2 +- test/data/plugins/errorMissName.js | 2 +- test/data/plugins/errorMissVersion.js | 2 +- test/data/plugins/plugindemo.js | 7 ++- test/loader.ts | 14 ++--- 9 files changed, 104 insertions(+), 65 deletions(-) diff --git a/src/loader.ts b/src/loader.ts index 9801cdef..f21c320e 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -207,7 +207,7 @@ function getFunctionModulePath(codeLocation: string): string | null { export async function getUserPlugins( options: FrameworkOptions ): Promise { - // get plugin set + // Get plugin set const pluginSet: Set = new Set(); if (options.context) { if (options.context.prePlugins) { @@ -223,44 +223,53 @@ export async function getUserPlugins( try { type Instance = Record; - // load plugin js files + // Load plugin js files const instances: Instance = {}; - const param = path.resolve(`${options.sourceLocation}/plugins`); - const plugin_files: Array = []; - const files = fs.readdirSync(param); - for (const k in files) { - plugin_files.push(require.resolve(path.join(param, files[k]))); + const pluginFiles = getPluginFiles(options.sourceLocation); + if (pluginFiles === null) { + console.warn('[warn-!!!] user plugins files load failed '); + options.context.prePlugins = []; + options.context.postPlugins = []; + return options; } - // find plugins class + // Find plugins class type PluginClass = Record; const tempMap: PluginClass = {}; - for (const k in plugin_files) { - const jsMoulde = require(plugin_files[k]); - if (jsMoulde && jsMoulde.Name) { - tempMap[jsMoulde.Name] = jsMoulde; + for (const pluginFile of pluginFiles) { + const jsMoulde = require(pluginFile); + for (const pluginClass in jsMoulde) { + if (jsMoulde[pluginClass].Name) { + tempMap[jsMoulde[pluginClass].Name] = jsMoulde[pluginClass]; + } } } - // instance plugin dynamic set ofn_plugin_name - const arr = Array.from(pluginSet.values()); - for (const k in arr) { - const module = tempMap[arr[k]]; + // Instance plugin dynamic set ofn_plugin_name + const pluginNames = Array.from(pluginSet.values()); + for (const name of pluginNames) { + const module = tempMap[name]; if (module) { const instance = new module(); - instance['ofn_plugin_name'] = module.Name; - instance['ofn_plugin_version'] = module.Version || 'v1'; + instance[Plugin.OFN_PLUGIN_NAME] = module.Name; + instance[Plugin.OFN_PLUGIN_VERSION] = module.Version || 'v1'; - //set default method of pre post get + //Set default method of pre post get if (!instance.execPreHook) { - instance.execPreHook = (ctx?: OpenFunctionRuntime) => { - console.log(ctx); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + instance.execPreHook = (ctx: OpenFunctionRuntime) => { + console.log( + `This plugin ${name} method execPreHook is not implemented.` + ); }; } if (!instance.execPostHook) { - instance.execPostHook = (ctx?: OpenFunctionRuntime) => { - console.log(ctx); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + instance.execPostHook = (ctx: OpenFunctionRuntime) => { + console.log( + `This plugin ${name} method execPostHook is not implemented.` + ); }; } if (!instance.get) { @@ -272,7 +281,7 @@ export async function getUserPlugins( } }; } - instances[arr[k]] = instance as Plugin; + instances[name] = instance as Plugin; } } @@ -298,8 +307,32 @@ export async function getUserPlugins( options.context.prePlugins = prePlugins; options.context.postPlugins = postPlugins; } catch (error) { + console.error('load plugins error reason: \n'); console.error(error); } } return options; } + +/** + * Returns resolved path to the dir containing the user plugins. + * Returns null if the path is not exits + * @param codeLocation Directory with user's code. + * @return Resolved path or null. + */ +function getPluginFiles(codeLocation: string): Array | null { + const pluginFiles: Array = []; + try { + const param = path.resolve(codeLocation + '/plugins'); + const files = fs.readdirSync(param); + + for (const file of files) { + pluginFiles.push(require.resolve(path.join(param, file))); + } + } catch (ex) { + const err: Error = ex; + console.error(err.message); + return null; + } + return pluginFiles; +} diff --git a/src/main.ts b/src/main.ts index 41686ae3..c42ff02a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -54,14 +54,13 @@ export const main = async () => { if (ContextUtils.IsAsyncRuntime(options.context as OpenFunctionContext)) { options.context!.port = options.port; + const server = getAysncServer( userFunction! as OpenFunction, options.context! ); - server.start().then(async () => { - // load and instance Plugins - await getUserPlugins(options); - }); + await server.start(); + await getUserPlugins(options); } else { const server = getServer(userFunction!, signatureType, options.context); const errorHandler = new ErrorHandler(server); diff --git a/src/openfunction/async_server.ts b/src/openfunction/async_server.ts index ee5728b8..6107f24e 100644 --- a/src/openfunction/async_server.ts +++ b/src/openfunction/async_server.ts @@ -1,4 +1,4 @@ -import {forEach} from 'lodash'; +import {forEach, invoke} from 'lodash'; import {DaprServer} from '@dapr/dapr'; import {OpenFunction} from '../functions'; @@ -23,29 +23,23 @@ export default function ( const ctx = OpenFunctionRuntime.ProxyContext(context); const wrapper = async (data: object) => { - try { - // exec pre hooks - if (context.prePlugins) { - for (const p of context.prePlugins) { - if (p && typeof p === 'object') { - typeof p.execPreHook === 'function' && (await p.execPreHook(ctx)); - } - } - } + // Exec pre hooks + console.log(context.prePlugins); + if (context.prePlugins) { + await context.prePlugins.reduce(async (_, current) => { + await invoke(current, 'execPreHook', ctx); + return []; + }, Promise.resolve([])); + } - await userFunction(ctx, data); + await userFunction(ctx, data); - // exec post hooks - if (context.postPlugins) { - for (const p of context.postPlugins) { - if (p && typeof p === 'object') { - typeof p.execPostHook === 'function' && (await p.execPostHook(ctx)); - } - } - } - } catch (error) { - // if don't catch error process will down - console.error(error); + // Exec post hooks + if (context.postPlugins) { + await context.postPlugins.reduce(async (_, current) => { + await invoke(current, 'execPostHook', ctx); + return []; + }, Promise.resolve([])); } }; diff --git a/src/openfunction/function_context.ts b/src/openfunction/function_context.ts index dbde4535..fbf4dd23 100644 --- a/src/openfunction/function_context.ts +++ b/src/openfunction/function_context.ts @@ -155,19 +155,31 @@ export class ContextUtils { * @public */ export class Plugin { + static OFN_PLUGIN_NAME = 'ofn_plugin_name'; + static OFN_PLUGIN_VERSION = 'ofn_plugin_version'; /** * pre main function exec. * @param ctx - The openfunction runtime . */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars public async execPreHook(ctx?: OpenFunctionRuntime) { - console.log(ctx); + console.log( + `This plugin ${this.get( + Plugin.OFN_PLUGIN_NAME + )} method execPreHook is not implemented.` + ); } /** * post main function exec. * @param ctx - The openfunction runtime . */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars public async execPostHook(ctx?: OpenFunctionRuntime) { - console.log(ctx); + console.log( + `This plugin ${this.get( + Plugin.OFN_PLUGIN_NAME + )} method execPostHook is not implemented.` + ); } /** * get instance filed value. diff --git a/test/data/plugins/errorMissAll.js b/test/data/plugins/errorMissAll.js index 413b0248..a8736a1f 100644 --- a/test/data/plugins/errorMissAll.js +++ b/test/data/plugins/errorMissAll.js @@ -3,4 +3,4 @@ class ErrorPlugin{ static Name = "error-miss-all-plugin"; } -module.exports = ErrorPlugin; +module.exports = {ErrorPlugin}; diff --git a/test/data/plugins/errorMissName.js b/test/data/plugins/errorMissName.js index 667d89f1..c1266036 100644 --- a/test/data/plugins/errorMissName.js +++ b/test/data/plugins/errorMissName.js @@ -19,4 +19,4 @@ class ErrorPlugin{ } } -module.exports = ErrorPlugin; +module.exports = {ErrorPlugin}; diff --git a/test/data/plugins/errorMissVersion.js b/test/data/plugins/errorMissVersion.js index 0315b835..aaf3087c 100644 --- a/test/data/plugins/errorMissVersion.js +++ b/test/data/plugins/errorMissVersion.js @@ -19,4 +19,4 @@ class ErrorPlugin{ } } -module.exports = ErrorPlugin; +module.exports = {ErrorPlugin}; diff --git a/test/data/plugins/plugindemo.js b/test/data/plugins/plugindemo.js index 8c8a6dae..6fd4ea12 100644 --- a/test/data/plugins/plugindemo.js +++ b/test/data/plugins/plugindemo.js @@ -12,9 +12,9 @@ class DemoPlugin{ console.log(`-----------demo plugin pre hook-----------`); ctx['pre'] = 'pre-exec'; await sleep(); - console.log(`-----------sleep 3----------`) + console.log(`-----------pre sleep 3----------`) } - execPostHook(ctx){ + async execPostHook(ctx){ console.log(`-----------demo plugin post hook-----------`); ctx['post'] = 'post-exec'; console.log(`-----------send post-----------`); @@ -28,4 +28,5 @@ class DemoPlugin{ } } -module.exports = DemoPlugin; +// module.exports = {DemoPlugin}; +exports.DemoPlugin = DemoPlugin; diff --git a/test/loader.ts b/test/loader.ts index b792ada8..0fa3a119 100644 --- a/test/loader.ts +++ b/test/loader.ts @@ -19,6 +19,7 @@ import * as functions from '../src/functions'; import * as loader from '../src/loader'; import * as FunctionRegistry from '../src/function_registry'; import {FrameworkOptions} from '../src/options'; +import {Plugin} from '../src'; describe('loading function', () => { interface TestData { @@ -138,8 +139,7 @@ describe('loading plugins', () => { prePlugins: Array; postPlugins: Array; } - const ofn_plugin_name = 'ofn_plugin_name'; - const ofn_plugin_version = 'ofn_plugin_version'; + interface TestData { options: FrameworkOptions; except: ExceptData; @@ -257,13 +257,13 @@ describe('loading plugins', () => { options.context!.prePlugins!.forEach(item => { assert(typeof item === 'object'); - assert(item.get(ofn_plugin_version) === 'v1'); - current.prePlugins.push(item.get(ofn_plugin_name)); + assert(item.get(Plugin.OFN_PLUGIN_VERSION) === 'v1'); + current.prePlugins.push(item.get(Plugin.OFN_PLUGIN_NAME)); }); options.context!.postPlugins!.forEach(item => { assert(typeof item === 'object'); - assert(item.get(ofn_plugin_version) === 'v1'); - current.postPlugins.push(item.get(ofn_plugin_name)); + assert(item.get(Plugin.OFN_PLUGIN_VERSION) === 'v1'); + current.postPlugins.push(item.get(Plugin.OFN_PLUGIN_NAME)); }); assert.deepStrictEqual(current, test.except); @@ -305,7 +305,7 @@ describe('loading plugins', () => { const options = await loader.getUserPlugins(data.options); assert(typeof options.context!.prePlugins![0] === 'object'); assert( - options.context!.prePlugins![0].get('ofn_plugin_name') === + options.context!.prePlugins![0].get(Plugin.OFN_PLUGIN_NAME) === 'error-miss-all-plugin' ); assert(options.context!.prePlugins![0].execPreHook); From fa7c1f5790cca7478e439a2bd0926309224fa100 Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Wed, 3 Aug 2022 15:02:05 +0800 Subject: [PATCH 13/16] =?UTF-8?q?=F0=9F=8E=A8=20imporove=20plugin=20loader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: yad <459647480@qq.com> --- src/loader.ts | 42 ++++++++++++++++++++++++++----- test/data/plugins/errorMissAll.js | 2 +- test/loader.ts | 23 +++++++++++++++-- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/loader.ts b/src/loader.ts index f21c320e..5e943c5f 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -199,6 +199,7 @@ function getFunctionModulePath(codeLocation: string): string | null { return path; } +type PluginClass = Record; /** * Returns user's plugin from function file. * Returns null if plugin can't be retrieved. @@ -235,15 +236,10 @@ export async function getUserPlugins( } // Find plugins class - type PluginClass = Record; const tempMap: PluginClass = {}; for (const pluginFile of pluginFiles) { const jsMoulde = require(pluginFile); - for (const pluginClass in jsMoulde) { - if (jsMoulde[pluginClass].Name) { - tempMap[jsMoulde[pluginClass].Name] = jsMoulde[pluginClass]; - } - } + processJsModule(jsMoulde, tempMap); } // Instance plugin dynamic set ofn_plugin_name @@ -336,3 +332,37 @@ function getPluginFiles(codeLocation: string): Array | null { } return pluginFiles; } +/** + * Returns rdetermine whether it is a class. + * Returns boolean is can be class + * @param obj jsmodule. + * @return boolean of it is a class. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function couldBeClass(obj: any): boolean { + return ( + typeof obj === 'function' && + obj.prototype !== undefined && + obj.prototype.constructor === obj && + obj.toString().slice(0, 5) === 'class' + ); +} + +/** + * Process jsMoulde if it can be a plugin class put it into tempMap. + * @param obj jsmodule. + * @param tempMap PluginClass. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function processJsModule(obj: any, tempMap: PluginClass) { + if (typeof obj === 'object') { + for (const o in obj) { + if (couldBeClass(obj[o]) && obj[o].Name) { + tempMap[obj[o].Name] = obj[o]; + } + } + } + if (couldBeClass(obj) && obj.Name) { + tempMap[obj.Name] = obj; + } +} diff --git a/test/data/plugins/errorMissAll.js b/test/data/plugins/errorMissAll.js index a8736a1f..413b0248 100644 --- a/test/data/plugins/errorMissAll.js +++ b/test/data/plugins/errorMissAll.js @@ -3,4 +3,4 @@ class ErrorPlugin{ static Name = "error-miss-all-plugin"; } -module.exports = {ErrorPlugin}; +module.exports = ErrorPlugin; diff --git a/test/loader.ts b/test/loader.ts index 0fa3a119..35bc528c 100644 --- a/test/loader.ts +++ b/test/loader.ts @@ -236,12 +236,12 @@ describe('loading plugins', () => { name: 'error', version: '', runtime: 'ASYNC', - prePlugins: ['error-miss-version-plugin'], + prePlugins: ['error-miss-version-plugin', 'demo-plugin'], postPlugins: ['error-miss-version-plugin'], }, }, except: { - prePlugins: ['error-miss-version-plugin'], + prePlugins: ['error-miss-version-plugin', 'demo-plugin'], postPlugins: ['error-miss-version-plugin'], }, }, @@ -311,4 +311,23 @@ describe('loading plugins', () => { assert(options.context!.prePlugins![0].execPreHook); assert(options.context!.prePlugins![0].execPostHook); }); + + it('load multi plugins ', async () => { + const data: FrameworkOptions = { + port: '8080', + target: 'helloWorld', + sourceLocation: process.cwd() + '/test/data', + signatureType: 'event', + printHelp: false, + context: { + name: 'demo', + version: '', + runtime: 'ASYNC', + prePlugins: ['demo-plugin', 'error-miss-all-plugin'], + postPlugins: ['demo-plugin', 'error-miss-all-plugin'], + }, + }; + assert.ok(await loader.getUserPlugins(data)); + console.log(data); + }); }); From 37a91afc36e578ff5a4c72847fc334a7dc5b61b1 Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Wed, 3 Aug 2022 18:47:34 +0800 Subject: [PATCH 14/16] process conflict Signed-off-by: yad <459647480@qq.com> --- src/main.ts | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/main.ts b/src/main.ts index c42ff02a..bee7a1c1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,17 +16,22 @@ // Functions framework entry point that configures and starts Node.js server // that runs user's code on HTTP request. -import {getUserFunction, getUserPlugins} from './loader'; -import {ErrorHandler} from './invoker'; -import {getServer} from './server'; -import {parseOptions, helpText, OptionsError} from './options'; -import {OpenFunction} from './functions'; +import * as process from 'process'; + +import {createHttpTerminator} from 'http-terminator'; + import getAysncServer from './openfunction/async_server'; import { OpenFunctionContext, ContextUtils, } from './openfunction/function_context'; +import {getUserFunction} from './loader'; +import {ErrorHandler} from './invoker'; +import {getServer} from './server'; +import {parseOptions, helpText, OptionsError} from './options'; +import {OpenFunction} from './functions'; + /** * Main entrypoint for the functions framework that loads the user's function * and starts the HTTP server. @@ -39,7 +44,6 @@ export const main = async () => { console.error(helpText); return; } - const loadedFunction = await getUserFunction( options.sourceLocation, options.target, @@ -52,6 +56,8 @@ export const main = async () => { } const {userFunction, signatureType} = loadedFunction; + // Try to determine the server runtime + // Considering the async runtime in the first place if (ContextUtils.IsAsyncRuntime(options.context as OpenFunctionContext)) { options.context!.port = options.port; @@ -60,8 +66,12 @@ export const main = async () => { options.context! ); await server.start(); - await getUserPlugins(options); - } else { + + // DaprServer uses httpTerminator in server.stop() + handleShutdown(async () => await server.stop()); + } + // Then taking sync runtime as the fallback + else { const server = getServer(userFunction!, signatureType, options.context); const errorHandler = new ErrorHandler(server); server @@ -75,6 +85,12 @@ export const main = async () => { } }) .setTimeout(0); // Disable automatic timeout on incoming connections. + + // Create and use httpTerminator for Express + const terminator = createHttpTerminator({ + server, + }); + handleShutdown(async () => await terminator.terminate()); } } catch (e) { if (e instanceof OptionsError) { @@ -88,3 +104,15 @@ export const main = async () => { // Call the main method to load the user code and start the http server. main(); + +function handleShutdown(handler: () => Promise): void { + if (!handler) return; + + const shutdown = async (code: string) => { + console.log(`🛑 Terminating OpenFunction server on code ${code}...`); + await handler(); + }; + + process.on('SIGTERM', shutdown); + process.on('SIGINT', shutdown); +} From 75d2422326697045046bacacd891bec47dd48d6d Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Wed, 3 Aug 2022 18:56:34 +0800 Subject: [PATCH 15/16] resolve conflict Signed-off-by: yad <459647480@qq.com> --- src/main.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index bee7a1c1..3cc5f864 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,7 +26,7 @@ import { ContextUtils, } from './openfunction/function_context'; -import {getUserFunction} from './loader'; +import {getUserFunction, getUserPlugins} from './loader'; import {ErrorHandler} from './invoker'; import {getServer} from './server'; import {parseOptions, helpText, OptionsError} from './options'; @@ -69,6 +69,9 @@ export const main = async () => { // DaprServer uses httpTerminator in server.stop() handleShutdown(async () => await server.stop()); + + // Load Plugins + await getUserPlugins(options); } // Then taking sync runtime as the fallback else { From 840380390a22f95c807291fa7b6f37d40434bad4 Mon Sep 17 00:00:00 2001 From: yad <459647480@qq.com> Date: Thu, 4 Aug 2022 12:35:54 +0800 Subject: [PATCH 16/16] add api docs Signed-off-by: yad <459647480@qq.com> --- docs/generated/api.json | 58 +++++++++++++++++++++++++++++++++++++++++ docs/generated/api.md | 4 +++ 2 files changed, 62 insertions(+) diff --git a/docs/generated/api.json b/docs/generated/api.json index f315a66f..3180b5a5 100644 --- a/docs/generated/api.json +++ b/docs/generated/api.json @@ -2737,6 +2737,64 @@ ], "isOptional": false, "name": "get" + }, + { + "kind": "Property", + "canonicalReference": "@openfunction/functions-framework!Plugin_2.OFN_PLUGIN_NAME:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "static OFN_PLUGIN_NAME: " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": false, + "releaseTag": "Public", + "name": "OFN_PLUGIN_NAME", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": true, + "isProtected": false + }, + { + "kind": "Property", + "canonicalReference": "@openfunction/functions-framework!Plugin_2.OFN_PLUGIN_VERSION:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "static OFN_PLUGIN_VERSION: " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": false, + "releaseTag": "Public", + "name": "OFN_PLUGIN_VERSION", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": true, + "isProtected": false } ], "implementsTokenRanges": [] diff --git a/docs/generated/api.md b/docs/generated/api.md index 565e4721..e084c92b 100644 --- a/docs/generated/api.md +++ b/docs/generated/api.md @@ -165,6 +165,10 @@ class Plugin_2 { execPostHook(ctx?: OpenFunctionRuntime): Promise; execPreHook(ctx?: OpenFunctionRuntime): Promise; get(filedName: string): string; + // (undocumented) + static OFN_PLUGIN_NAME: string; + // (undocumented) + static OFN_PLUGIN_VERSION: string; } export { Plugin_2 as Plugin }