Skip to content

Commit

Permalink
feat: use native node loading for plugins + docs
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
coopernetes committed Jul 6, 2024
1 parent 5401677 commit b30689e
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 81 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ customize for your environment, see the [project's documentation](https://git-pr

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.

## Extensibility
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).

## Security

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.
Expand Down
7 changes: 7 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@
"description": "Flag to enable CSRF protections for UI",
"type": "boolean"
},
"pushPlugins": {
"type": "array",
"description": "List of plugins to run on push. Each value is either a file path or a module name.",
"items": {
"type": "string"
}
},
"authorisedList": {
"description": "List of repositories that are authorised to be pushed to through the proxy.",
"type": "array",
Expand Down
11 changes: 0 additions & 11 deletions packages/git-proxy-notify-hello/index.js

This file was deleted.

98 changes: 98 additions & 0 deletions plugins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Plugins
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.

## Loading plugins
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.

### NPM
1. Add the plugin package as a dependency.
```json
{
"name": "@finos/git-proxy",
...
"dependencies: {
"foo-my-gitproxy-plugin": "^0.0.1",
"@bar/another-gitproxy-plugin": "^0.0.1",
}
}
```

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/`.

```json
{
"pushPlugins": [
"foo-my-gitproxyplugin",
"@bar/another-gitproxy-plugin"
]
}
```

### Local files
1. Download the plugin's source files & run `npm install` to download any dependencies of the plugin itself.
2. Set the "pushPlugins" property in proxy.config.json to a list of files to load into Git Proxy.
```json
{
"pushPlugins": [
"./plugins/foo/index.js",
"/home/alice/gitproxy-push-plugin/index.js"
]
}
```

### Environment variables (deprecated)
The previous implementation of plugins were loaded via the following two environment variables:

- `GITPROXY_PLUGIN_FILES`: a list of comma-separated JavaScript files which point to Node modules that contain plugin objects
- `GITPROXY_PLUGIN_PACKAGES`: a list of comma-separated NPM packages which contain modules & plugin objects

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.

```bash
# Setting a list of plugin packages to load via env var when running git-proxy
$ export GITPROXY_PLUGIN_PACKAGES="foo-my-gitproxyplugin,@bar/another-gitproxy-plugin/src/plugins/baz"
$ npx -- @finos/git-proxy
```

## Writing plugins
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.

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.

To write a custom plugin, import the `PushActionPlugin` class from `@finos/git-proxy` and create a new type with your custom function:

```javascript
// plugin-foo/index.js
const PushActionPlugin = require('@finos/git-proxy/src/plugin').PushActionPlugin;

class MyPlugin extends PushActionPlugin {
constructor() {
super((req, action) => {
console.log(req); // Log the express.Request object
// insert custom behaviour here using the Action object...
return action;
})
}
}

module.exports = new MyPlugin();
```

> Note: use `peerDependencies` to depend on `@finos/git-proxy` in your plugin's package to avoid circular dependencies!
## Sample plugin
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:

1. Run `npm install` in [./plugins/git-proxy-hello-world](./git-proxy-hello-world/).
2. Set "pushPlugins" in `proxy.config.json`:
```json
{
"pushPlugins": [
"./plugins/git-proxy-hello-world/index.js"
]
}
```
3. Run Git Proxy from source:
```
npm run start
```
43 changes: 43 additions & 0 deletions plugins/git-proxy-hello-world/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const Step = require('@finos/git-proxy/src/proxy/actions').Step;
// eslint-disable-next-line no-unused-vars
const Action = require('@finos/git-proxy/src/proxy/actions').Action;
const ActionPlugin = require('@finos/git-proxy/src/plugin').ActionPlugin;
'use strict';

class HelloPlugin extends ActionPlugin {
constructor() {
super(function logMessage(req, action) {
const step = new Step('HelloPlugin');
action.addStep(step);
console.log('This is a message from the HelloPlugin!');
return action;
})
}
}

/**
*
* @param {Request} req
* @param {Action} action
* @return {Promise<Action>} Promise that resolves to an Action
*/
async function logMessage(req, action) {
const step = new Step('LogRequestPlugin');
action.addStep(step);
console.log(`LogRequestPlugin: req url ${req.url}`);
console.log('LogRequestPlugin: action', JSON.stringify(action));
return action;
}

class LogRequestPlugin extends ActionPlugin {
constructor() {
super(logMessage)
}

}


module.exports = {
hello: new HelloPlugin(),
logRequest: new LogRequestPlugin()
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
"author": "Thomas Cooper",
"license": "Apache-2.0",
"dependencies": {
"@finos/git-proxy": "file:../.."
"express": "^4.18.2"
},
"peerDependencies": {
"@finos/git-proxy": "^1.3.2"
}
}
3 changes: 2 additions & 1 deletion proxy.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,6 @@
"privateOrganizations": [],
"urlShortener": "",
"contactEmail": "",
"csrfProtection": true
"csrfProtection": true,
"pushPlugins": []
}
7 changes: 7 additions & 0 deletions src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const _privateOrganizations = defaultSettings.privateOrganizations;
const _urlShortener = defaultSettings.urlShortener;
const _contactEmail = defaultSettings.contactEmail;
const _csrfProtection = defaultSettings.csrfProtection;
const _pushPlugins = defaultSettings.pushPlugins;

// Get configured proxy URL
const getProxyUrl = () => {
Expand Down Expand Up @@ -142,6 +143,11 @@ const getCSRFProtection = () => {
return _csrfProtection;
};

// Get loadable push plugins
const getPushPlugins = () => {
return _pushPlugins;
}

const getSSLKeyPath = () => {
if (_userSettings && _userSettings.sslKeyPemPath) {
_sslKeyPath = _userSettings.sslKeyPemPath;
Expand Down Expand Up @@ -177,5 +183,6 @@ exports.getPrivateOrganizations = getPrivateOrganizations;
exports.getURLShortener = getURLShortener;
exports.getContactEmail = getContactEmail;
exports.getCSRFProtection = getCSRFProtection;
exports.getPushPlugins = getPushPlugins;
exports.getSSLKeyPath = getSSLKeyPath;
exports.getSSLCertPath = getSSLCertPath;
Loading

0 comments on commit b30689e

Please sign in to comment.