-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #749 from snyk/feat/implement-client-plugin-system
feat: add broker plugin system [HYB-516]
- Loading branch information
Showing
30 changed files
with
1,366 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"BROKER_CLIENT_CONFIGURATION": { | ||
"common": { | ||
"default": { | ||
"BROKER_SERVER_URL": "https://broker.pre-prod.snyk.io", | ||
"BROKER_HA_MODE_ENABLED": "false", | ||
"BROKER_DISPATCHER_BASE_URL": "https://api.pre-prod.snyk.io" | ||
}, | ||
"oauth": { | ||
"clientId": "${CLIENT_ID}", | ||
"clientSecret": "${CLIENT_SECRET}" | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { | ||
HttpResponse, | ||
makeRequestToDownstream, | ||
} from '../../common/http/request'; | ||
import { PostFilterPreparedRequest } from '../../common/relay/prepareRequest'; | ||
import { log as logger } from '../../logs/logger'; | ||
|
||
export default abstract class BrokerPlugin { | ||
abstract pluginCode: string; | ||
abstract pluginName: string; | ||
abstract description: string; | ||
abstract version: string; | ||
abstract applicableBrokerTypes: Array<string>; | ||
logger; | ||
brokerClientConfiguration: Record<string, any>; | ||
makeRequestToDownstream: ( | ||
req: PostFilterPreparedRequest, | ||
retries?: any, | ||
) => Promise<HttpResponse>; | ||
request?: PostFilterPreparedRequest; | ||
|
||
constructor(brokerClientCfg: Record<string, any>) { | ||
this.logger = logger; | ||
this.brokerClientConfiguration = brokerClientCfg; | ||
this.makeRequestToDownstream = makeRequestToDownstream; | ||
} | ||
|
||
getApplicableTypes(): Array<string> { | ||
const applicableTypes: Array<string> = []; | ||
if ( | ||
this.applicableBrokerTypes.every((type) => | ||
this.brokerClientConfiguration.supportedBrokerTypes.includes(type), | ||
) | ||
) { | ||
applicableTypes.push(...this.applicableBrokerTypes); | ||
} | ||
return applicableTypes; | ||
} | ||
isDisabled(config): boolean { | ||
let isDisabled = false; | ||
if (config[`DISABLE_${this.pluginCode}_PLUGIN`]) { | ||
logger.info({ plugin: this.pluginName }, `Plugin disabled`); | ||
isDisabled = true; | ||
} | ||
return isDisabled; | ||
} | ||
abstract isPluginActive(): boolean; | ||
|
||
abstract startUp(connectionConfiguration: Record<string, any>): Promise<void>; | ||
|
||
async preRequest( | ||
connectionConfiguration: Record<string, any>, | ||
postFilterPreparedRequest: PostFilterPreparedRequest, | ||
): Promise<PostFilterPreparedRequest> { | ||
return postFilterPreparedRequest; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import { readdir } from 'fs/promises'; | ||
import { log as logger } from '../../logs/logger'; | ||
import BrokerPlugin from './abstractBrokerPlugin'; | ||
import { existsSync } from 'fs'; | ||
import { PostFilterPreparedRequest } from '../../common/relay/prepareRequest'; | ||
|
||
export const loadPlugins = async (pluginsFolderPath: string, clientOpts) => { | ||
clientOpts.config['plugins'] = new Map<string, unknown>(); | ||
clientOpts.config.supportedBrokerTypes.forEach((type) => { | ||
clientOpts.config.plugins.set(type, []); | ||
}); | ||
try { | ||
if (existsSync(pluginsFolderPath)) { | ||
const pluginsFiles = await readdir(pluginsFolderPath); | ||
for (const pluginFile of pluginsFiles.filter((filename) => | ||
filename.endsWith('.js'), | ||
)) { | ||
const plugin = await import(`${pluginsFolderPath}/${pluginFile}`); | ||
// Passing the config object so we can mutate things like filters instead of READONLY | ||
const pluginInstance = new plugin.Plugin(clientOpts.config); | ||
const applicableBrokerTypes = pluginInstance.getApplicableTypes(); | ||
applicableBrokerTypes.forEach((applicableBrokerType) => { | ||
if ( | ||
!pluginInstance.isDisabled(clientOpts.config) && | ||
pluginInstance.isPluginActive() | ||
) { | ||
logger.debug({}, `Loading plugin ${pluginInstance.pluginName}`); | ||
const configPluginForCurrentType = | ||
clientOpts.config.plugins.get(applicableBrokerType); | ||
if ( | ||
configPluginForCurrentType.some( | ||
(x) => | ||
x.pluginCode === pluginInstance.pluginCode || | ||
x.pluginName === pluginInstance.pluginName, | ||
) | ||
) { | ||
const errMsg = `Some Plugins have identical name or code.`; | ||
logger.error({}, errMsg); | ||
throw new Error(errMsg); | ||
} | ||
configPluginForCurrentType.push(pluginInstance); | ||
} else { | ||
logger.debug( | ||
{}, | ||
`Skipping plugin ${pluginInstance.pluginName}, not active.`, | ||
); | ||
} | ||
}); | ||
} | ||
} | ||
return clientOpts.config['plugins']; | ||
} catch (err) { | ||
const errMsg = `Error loading plugins from ${pluginsFolderPath}`; | ||
logger.error({ err }, `Error loading plugins from ${pluginsFolderPath}`); | ||
throw new Error(errMsg); | ||
} | ||
}; | ||
|
||
export const runStartupPlugins = async (clientOpts) => { | ||
const loadedPlugins = clientOpts.config.plugins as Map< | ||
string, | ||
BrokerPlugin[] | ||
>; | ||
|
||
const connectionsKeys = clientOpts.config.connections | ||
? Object.keys(clientOpts.config.connections) | ||
: []; | ||
|
||
for (const connectionKey of connectionsKeys) { | ||
if ( | ||
loadedPlugins.has(`${clientOpts.config.connections[connectionKey].type}`) | ||
) { | ||
const pluginInstances = | ||
loadedPlugins.get( | ||
`${clientOpts.config.connections[connectionKey].type}`, | ||
) ?? []; | ||
for (let i = 0; i < pluginInstances.length; i++) { | ||
await pluginInstances[i].startUp( | ||
clientOpts.config.connections[connectionKey], | ||
); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
export const runPreRequestPlugins = async ( | ||
clientOpts, | ||
connectionIdentifier, | ||
pristinePreRequest: PostFilterPreparedRequest, | ||
) => { | ||
let preRequest = pristinePreRequest; | ||
const loadedPlugins = clientOpts.config.plugins as Map< | ||
string, | ||
BrokerPlugin[] | ||
>; | ||
const connectionsKeys = Object.keys(clientOpts.config.connections); | ||
let connectionKey; | ||
for (let i = 0; i < connectionsKeys.length; i++) { | ||
if ( | ||
clientOpts.config.connections[connectionsKeys[i]].identifier == | ||
connectionIdentifier | ||
) { | ||
connectionKey = connectionsKeys[i]; | ||
break; | ||
} | ||
} | ||
if (!connectionsKeys.includes(connectionKey)) { | ||
const errMsg = `Plugin preRequest: connection ${connectionKey} not found`; | ||
logger.error({ connectionKey }, errMsg); | ||
throw new Error(errMsg); | ||
} | ||
|
||
if ( | ||
loadedPlugins.has(`${clientOpts.config.connections[connectionKey].type}`) | ||
) { | ||
const pluginInstances = | ||
loadedPlugins.get( | ||
`${clientOpts.config.connections[connectionKey].type}`, | ||
) ?? []; | ||
for (let i = 0; i < pluginInstances.length; i++) { | ||
preRequest = await pluginInstances[i].preRequest( | ||
clientOpts.config.connections[connectionKey], | ||
preRequest, | ||
); | ||
} | ||
} | ||
|
||
return preRequest; | ||
}; |
Oops, something went wrong.