Skip to main content

Plugins

How plugins work

GitProxy supports extensibility in the form of plugins. These plugins are specified via configuration as NPM packages or JavaScript code on disk. For each plugin configured, GitProxy will attempt to load each package or file as a standard Node module. Plugin authors will create instances of the extension classes exposed by GitProxy and use these objects to implement custom functionality.

For each loaded "plugin object", it is inserted into GitProxy's chain of actions which are triggered on a given Git action received by GitProxy such as git push or git fetch.

caution

The order that plugins are configured matters! Plugins execute before GitProxy's builtin steps and in the order that they are configured in proxy.config.json. If you wish to use a combination of features, ensure that your custom plugins do not conflict or interfere with later steps in the processing chain.

GitProxy uses the load-plugin package to provide the Node module resolution.

Limitations

  • Plugins are only supported on the Git HTTP proxy server. There is no similar extensibility today for the dashboard UI or its backing API.
  • Extensions are defined as JavaScript classes which are quite limited. GitProxy has a rather naive system for determining if a provided module has any objects which it recognizes as "GitProxy plugin types". A conversion of the project to TypeScript will provide more flexible options for enforcing API contracts via strict interfaces & types in a future release. Use of TypeScript is a roadmap item.

Using plugins

The primary goals of the plugin system is to:

  • Allow users deploying GitProxy to do so via the distributed binary package. ie. npx -- @finos/git-proxy
  • Allow users to consume plugins that packaged using standard tools (npm) and optionally distributed via npm themselves.
  • Reuse as much native Node functionality as possible for calling 3rd-party code (plugins) from the main application (git-proxy).

The below instructions are using the @finos/git-proxy-plugin-samples package as an demonstrative set of plugins.

via npm packages

Steps

  1. Install both @finos/git-proxy and the @finos/git-proxy-plugin-samples package to the local node_modules/ directory using the following command:
$ npm install -g @finos/git-proxy@latest @finos/git-proxy-plugin-samples@0.1.0

Alternatively, you can create local packages from source and install those instead.

$ git clone https://github.com/finos/git-proxy
$ cd git-proxy
$ npm pack
$ npm install -g ./finos-git-proxy-1.3.5.tgz
$ (cd plugins/git-proxy-plugin-samples && npm pack)
$ npm install -g plugins/git-proxy-plugin-samples/finos-git-proxy-plugin-samples-0.1.0.tgz
  1. Create or edit an existing proxy.config.json file to configure the plugin(s) to load. You must include the full import path that would typically be used in a import {} (ESM) or require() (CJS) statement:
{
"plugins": [
"@finos/git-proxy-plugin-samples",
"@finos/git-proxy-plugin-samples/example.cjs"
]
}
  1. Run git-proxy.
$ git-proxy
Service Listening on 8080
HTTP Proxy Listening on 8000
HTTPS Proxy Listening on 8443
Found 2 plugin modules
Loaded plugin: RunOnPullPlugin
Loaded plugin: HelloPlugin
Loaded plugin: LogRequestPlugin

via JavaScript file(s)

caution

This section is considered highly experimental and not recommended for general use. Even when authoring local plugins that are not distributed, it is best to use node_modules/ and not rely on bespoke system setup or layout of files. Use npm pack and npm install path/to/plugin.tgz if you must use local plugin code to ensure GitProxy's plugin manager can properly load your plugins.

Plugins written as standalone JavaScript files are used similarly to npm packages. The main difference is that file-based module loading requires additional steps to ensure that the given JavaScript files (written as Node CommonJS or ES modules) have all the necessary dependencies to be imported. Since GitProxy plugin system relies on class-based inheritence, the module will require at least a dependency to @finos/git-proxy to be able to import the required classes. Plugins do not have to be distributed by NPM to be used in this fashion which may be advantageous in certain environments.

  1. To use a plugin that is written as a standalone JavaScript file, ensure that the JS code has all its necessary dependencies:
$ cd path/to/plugindir
$ cat package.json
{
"name": "foo-plugin",
...
"dependencies": {
"@finos/git-proxy": "^1.3.5"
}
}
# Alternatively, add git-proxy that is cloned locally as a file-based dependency
$ cat package.json
{
"name": "foo-plugin",
"dependencies": {
"@finos/git-proxy": "file:/path/to/checked/out/finos/git-proxy"
}
}
$ npm install
  1. Create or edit an existing proxy.config.json file to configure the plugin(s) to load:
{
"plugins": [
"path/to/plugin/index.js"
]
}
  1. Run git-proxy
$ git-proxy
Service Listening on 8080
HTTP Proxy Listening on 8000
HTTPS Proxy Listening on 8443
Found 1 plugin modules
Loaded plugin: FooPlugin

Developing new plugins

To develop a new plugin, you must add @finos/git-proxy as a peer dependency. The main app (also known as the "host application") exports the following extension points:

  • @finos/git-proxy/src/plugin/PushActionPlugin: execute as an action in the proxy chain during a git push
  • @finos/git-proxy/src/plugin/PullActionPlugin: execute as an action in the proxy chain during a git fetch
  • @finos/git-proxy/src/proxy/actions/Step and @finos/git-proxy/src/proxy/actions/Action: internal classes which act as carriers for git state during proxying. Plugins should modify the passed in action for affecting any global state of the git operation and add its own custom Step object to capture the plugin's own internal state (logs, errored/blocked status, etc.)

GitProxy will load your plugin only if it extends one of the two plugin classes above. It is also important that your package has exports defined for the plugin loader to properly load your module(s).

Please see the sample plugin package included in the repo for details on how to structure your plugin package.

If your plugin relies on custom state, it is recommended to create subclasses in the following manner:

import { PushActionPlugin } from "@finos/git-proxy/src/plugin";

class FooPlugin extends PushActionPlugin {
constructor() {
super(); // don't pass any function to the parent class - let the parent's this.exec function be undefined
this.displayName = 'FooPlugin';
// overwrite the parent's exec function which is executed as part of GitProxy's action chain.
// use an arrow function if you require access to the instances's state (properties, methods, etc.)
this.exec = async (req, action) => {
console.log(this.displayName);
this.utilMethod();
// do something
return action;
}
}

utilMethod() {
// misc operations specific to FooPlugin
}
}

Example

$ npm init
# ...
$ npm install --save-peer @finos/git-proxy@latest

package.json

{
"name": "bar",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"description": "",
"exports": {
".": "./bar.js"
},
"peerDependencies": {
"@finos/git-proxy": "^1.3.5"
}
}

bar.js

import { PushActionPlugin } from "@finos/git-proxy/src/plugin";
import { Step } from "@finos/git-proxy/src/proxy/actions";

//Note: Only use a default export if you do not rely on any state. Otherwise, create a sub-class of [Push/Pull]ActionPlugin
export default new PushActionPlugin(function(req, action) {
// the action parameter holds all the details about a push.
// see https://github.com/finos/git-proxy/blob/main/src/proxy/actions/Action.js

// create a new Step to track any side effects of this plugin function (errors, blocking pushes, logs, etc)
// see https://github.com/finos/git-proxy/blob/main/src/proxy/actions/Step.js
const step = new Step('bar-plugin');
action.addStep(step);
// add custom logic here.
// ...
return action; // always return the action - even in the case of errors
});