Skip to content

Commit b30689e

Browse files
committed
feat: use native node loading for plugins + docs
- update PluginLoader to use load-plugin's native Node module resolution. this library already handles loading of files vs modules in node_modules/ so let's use that directly - add configuration-based plugin loading and deprecate environment variables - new documentation page for plugin development. loading, writing, etc.
1 parent 5401677 commit b30689e

File tree

11 files changed

+248
-81
lines changed

11 files changed

+248
-81
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ customize for your environment, see the [project's documentation](https://git-pr
9595

9696
Your contributions are at the core of making this a true open source project. Any contributions you make are **greatly appreciated**. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for more information.
9797

98+
## Extensibility
99+
Git Proxy exposes the ability to add custom functionality in the form of plugins which run during a git push. Plugins are loaded via configuration and can be added to a git-proxy deployment via a npm package or loaded from JavaScript files on disk. See [plugin documentation for details](./plugins/README.md).
100+
98101
## Security
99102

100103
If you identify a security vulnerability in the codebase, please follow the steps in [`SECURITY.md`](https://github.com/finos/git-proxy/security/policy). This includes logic-based vulnerabilities and sensitive information or secrets found in code.

config.schema.json

+7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@
3636
"description": "Flag to enable CSRF protections for UI",
3737
"type": "boolean"
3838
},
39+
"pushPlugins": {
40+
"type": "array",
41+
"description": "List of plugins to run on push. Each value is either a file path or a module name.",
42+
"items": {
43+
"type": "string"
44+
}
45+
},
3946
"authorisedList": {
4047
"description": "List of repositories that are authorised to be pushed to through the proxy.",
4148
"type": "array",

packages/git-proxy-notify-hello/index.js

-11
This file was deleted.

plugins/README.md

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Plugins
2+
Git Proxy has a simple mechanism for exposing customization within the application for organizations who wish to augment Git Proxy's built-in features, add custom functionality or integrate Git Proxy & other systems within their environment. Plugins are authored via custom objects & functions in JavaScript and can be distributed as NPM packages or source files. Plugins are loaded at deployment via configuration of the git-proxy application server.
3+
4+
## Loading plugins
5+
In order to load a plugin, you must either install the plugin as a standalone npm package by adding the plugin as a dependency or specify a file path on disk to load a `.js` file as a module.
6+
7+
### NPM
8+
1. Add the plugin package as a dependency.
9+
```json
10+
{
11+
"name": "@finos/git-proxy",
12+
...
13+
"dependencies: {
14+
"foo-my-gitproxy-plugin": "^0.0.1",
15+
"@bar/another-gitproxy-plugin": "^0.0.1",
16+
}
17+
}
18+
```
19+
20+
2. Set the "pushPlugins" property in proxy.config.json to a list of modules to load into Git Proxy. These packages must exist in `node_modules/`.
21+
22+
```json
23+
{
24+
"pushPlugins": [
25+
"foo-my-gitproxyplugin",
26+
"@bar/another-gitproxy-plugin"
27+
]
28+
}
29+
```
30+
31+
### Local files
32+
1. Download the plugin's source files & run `npm install` to download any dependencies of the plugin itself.
33+
2. Set the "pushPlugins" property in proxy.config.json to a list of files to load into Git Proxy.
34+
```json
35+
{
36+
"pushPlugins": [
37+
"./plugins/foo/index.js",
38+
"/home/alice/gitproxy-push-plugin/index.js"
39+
]
40+
}
41+
```
42+
43+
### Environment variables (deprecated)
44+
The previous implementation of plugins were loaded via the following two environment variables:
45+
46+
- `GITPROXY_PLUGIN_FILES`: a list of comma-separated JavaScript files which point to Node modules that contain plugin objects
47+
- `GITPROXY_PLUGIN_PACKAGES`: a list of comma-separated NPM packages which contain modules & plugin objects
48+
49+
Any files or packages specified by these variables will continue to be loaded via the plugin loader if set. However, it is recommended to simply list either files or NPM packages to load as plugins via configuration as documented above. These environment variables will be removed in a future release.
50+
51+
```bash
52+
# Setting a list of plugin packages to load via env var when running git-proxy
53+
$ export GITPROXY_PLUGIN_PACKAGES="foo-my-gitproxyplugin,@bar/another-gitproxy-plugin/src/plugins/baz"
54+
$ npx -- @finos/git-proxy
55+
```
56+
57+
## Writing plugins
58+
Plugins are written as Node modules which export objects containing the custom behaviour. These objects must extend the classes exported by Git Proxy's `plugin/` module. The only class which is exported today for developers to extend Git Proxy is called the `PushActionPlugin` class. This class executes the custom behaviour on any `git push` going through Git Proxy.
59+
60+
The `PushActionPlugin` class takes a single function into its constructor which is executed on a `git push`. This is then loaded into the push proxy's "chain" of actions. Custom plugins are executed after parsing of a push but before any builtin actions. It is important to be aware of the load order when writing plugins to ensure that one plugin does not conflict with another. The order specified in `pushPlugins` configuration setting is preserved by the loader.
61+
62+
To write a custom plugin, import the `PushActionPlugin` class from `@finos/git-proxy` and create a new type with your custom function:
63+
64+
```javascript
65+
// plugin-foo/index.js
66+
const PushActionPlugin = require('@finos/git-proxy/src/plugin').PushActionPlugin;
67+
68+
class MyPlugin extends PushActionPlugin {
69+
constructor() {
70+
super((req, action) => {
71+
console.log(req); // Log the express.Request object
72+
// insert custom behaviour here using the Action object...
73+
return action;
74+
})
75+
}
76+
}
77+
78+
module.exports = new MyPlugin();
79+
```
80+
81+
> Note: use `peerDependencies` to depend on `@finos/git-proxy` in your plugin's package to avoid circular dependencies!
82+
83+
## Sample plugin
84+
Git Proxy includes a sample plugin that can be loaded with any deployment for demonstration purposes. This plugin is not published as a standalone NPM package and must be used as a local file during deployment. To use the sample plugin:
85+
86+
1. Run `npm install` in [./plugins/git-proxy-hello-world](./git-proxy-hello-world/).
87+
2. Set "pushPlugins" in `proxy.config.json`:
88+
```json
89+
{
90+
"pushPlugins": [
91+
"./plugins/git-proxy-hello-world/index.js"
92+
]
93+
}
94+
```
95+
3. Run Git Proxy from source:
96+
```
97+
npm run start
98+
```
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const Step = require('@finos/git-proxy/src/proxy/actions').Step;
2+
// eslint-disable-next-line no-unused-vars
3+
const Action = require('@finos/git-proxy/src/proxy/actions').Action;
4+
const ActionPlugin = require('@finos/git-proxy/src/plugin').ActionPlugin;
5+
'use strict';
6+
7+
class HelloPlugin extends ActionPlugin {
8+
constructor() {
9+
super(function logMessage(req, action) {
10+
const step = new Step('HelloPlugin');
11+
action.addStep(step);
12+
console.log('This is a message from the HelloPlugin!');
13+
return action;
14+
})
15+
}
16+
}
17+
18+
/**
19+
*
20+
* @param {Request} req
21+
* @param {Action} action
22+
* @return {Promise<Action>} Promise that resolves to an Action
23+
*/
24+
async function logMessage(req, action) {
25+
const step = new Step('LogRequestPlugin');
26+
action.addStep(step);
27+
console.log(`LogRequestPlugin: req url ${req.url}`);
28+
console.log('LogRequestPlugin: action', JSON.stringify(action));
29+
return action;
30+
}
31+
32+
class LogRequestPlugin extends ActionPlugin {
33+
constructor() {
34+
super(logMessage)
35+
}
36+
37+
}
38+
39+
40+
module.exports = {
41+
hello: new HelloPlugin(),
42+
logRequest: new LogRequestPlugin()
43+
};

packages/git-proxy-notify-hello/package.json plugins/git-proxy-hello-world/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
"author": "Thomas Cooper",
99
"license": "Apache-2.0",
1010
"dependencies": {
11-
"@finos/git-proxy": "file:../.."
11+
"express": "^4.18.2"
12+
},
13+
"peerDependencies": {
14+
"@finos/git-proxy": "^1.3.2"
1215
}
1316
}

proxy.config.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -95,5 +95,6 @@
9595
"privateOrganizations": [],
9696
"urlShortener": "",
9797
"contactEmail": "",
98-
"csrfProtection": true
98+
"csrfProtection": true,
99+
"pushPlugins": []
99100
}

src/config/index.js

+7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const _privateOrganizations = defaultSettings.privateOrganizations;
2323
const _urlShortener = defaultSettings.urlShortener;
2424
const _contactEmail = defaultSettings.contactEmail;
2525
const _csrfProtection = defaultSettings.csrfProtection;
26+
const _pushPlugins = defaultSettings.pushPlugins;
2627

2728
// Get configured proxy URL
2829
const getProxyUrl = () => {
@@ -142,6 +143,11 @@ const getCSRFProtection = () => {
142143
return _csrfProtection;
143144
};
144145

146+
// Get loadable push plugins
147+
const getPushPlugins = () => {
148+
return _pushPlugins;
149+
}
150+
145151
const getSSLKeyPath = () => {
146152
if (_userSettings && _userSettings.sslKeyPemPath) {
147153
_sslKeyPath = _userSettings.sslKeyPemPath;
@@ -177,5 +183,6 @@ exports.getPrivateOrganizations = getPrivateOrganizations;
177183
exports.getURLShortener = getURLShortener;
178184
exports.getContactEmail = getContactEmail;
179185
exports.getCSRFProtection = getCSRFProtection;
186+
exports.getPushPlugins = getPushPlugins;
180187
exports.getSSLKeyPath = getSSLKeyPath;
181188
exports.getSSLCertPath = getSSLCertPath;

0 commit comments

Comments
 (0)