During the last weeks we’ve looked at multiple topics like connecting Strider with GitHub, GitLab, Bitbucket and test projects automatically. Additionally, we set up notification services as email, Slack and HipChat. Now it’s time to dig deep into Strider plugins and get a fundamental understanding how they work and how to create your own plugin.
Outline
Strider by default ships with a command line tool which creates a basic plugin template. You don’t have to check documentation (which isn’t existing to this date) how to structure your code and which interfaces to implement. Additionally, you can just clone the plugin template repository and make your changes. We’ll look at both options in the sections below.
Strider is a Node.js application and composes of plugins. The plugins itself are node module as well. Just place your plugin into the `node_modules´ folder of your Strider installation directory and Strider will recognize it during its start up.
The Strider ecosystem offers the cli plugin. It add several commands to your Strider deployment like add a new user, restart the server, list all available plugins, initialize a new plugin, etc.
Creating a new plugin with command line can be done with the following command:
bin/strider init
The creation process will ask you for a plugin name, description and author.
plugin: name: your-plugin-name
plugin: description: test
plugin: author: test
A strider plugin template has been prepared for you in the following directory
/Users/marcuspoehls/Dev/JS/strider/node_modules/template
Please view the README and begin editing the package.json.
Make sure to change the git remote to wherever you’re hosting your plugin source code
When you’re ready to publish, submit a pull request to https://github.com/Strider-CD/strider-plugins
If you have any questions or need help, you can find us in irc.freenode.net #strider
Open your config.js
file (located in the config
directory) and search for these lines:
$scope.$watch(‚configs[branch.name].template.config‘, function (value) {
$scope.config = value || configDefaults;
});
Adjust the first line and change .template.
to your plugin name.
$scope.$watch(‚configs[branch.name].your-plugin-name.config‘, function (value) {
$scope.config = value || configDefaults;
});
This will make your plugin work when activing for a project.
Besides Strider’s command line, you can just clone the plugin template repository and make your changes from here. In comparison to the command line way, you don’t have to adjust the files to get the basic plugin working right away.
git clone https://github.com/Strider-CD/strider-template.git
Just clone this repository and adjust the code. The next paragraph describes the recommended changes you should perform for your new plugin.
Each plugin in the Strider ecosystem requires either a strider.json
file in the plugin’s root directory or a „strider“ section within the package.json
. The content needs to be defined as JSON. We’ll explain each option in more detail.
Default plugin package.json
file
„strider“: {
„type“: „job“,
„id“: „futurestudio-plugin“,
„title“: „Future Studio Plugin“,
„worker“: „worker.js“,
„webapp“: „webapp.js“,
„icon“: „icon.png“,
„config“: {
„controller“: „FutureStudioPluginController“
},
}
The definition above is the recommended one. You can add further options if you need them for your plugin.
What plugin type to choose? This seems a tough question. Having a closer look, it isn’t. Strider’s extension loader offers four extension types to choose from. Depending on your plugin, define one in your package.json´ as
type`. - runner: runner plugins run your integration jobs - provider: provider plugins get the source code for your project from GitHub, GitLab, Bitbucket or just a git reposority hosted on your server - job: job plugins create the test environment, run tests and deployments - basic: this is the ”do what you want“ plugin type. You can do powerful things without any helper methods provided by Strider’s extension manager.
id: Every Strider plugin has its own id. This is the unique identifier for Strider’s Extension Loader. The required format: lowercase, alphanumeric, no spaces.
title: this property is just the name of your plugin.
worker: the path to your worker.js
file
webapp: the path to your webapp.js
file
icon: the path to your custom plugin icon
config: a nested JSON object with further definitions - controller: the name of your AngularJS-Controler which is used to save your plugin configuration. Has to be the same name as defined in your config.js
.
The readme of Strider’s Extension Loader provides additional comments for each plugin property.
Each plugin adds features to Strider and so will yours. We listed four plugin types. Strider executes different methods for each plugin type and requires them to be implemented.
Find more information which methods you need to define for each plugin type: runner, provider, job, basic.
We use the job plugin type for further explanations. Generating a new plugin from command line or using the plugin template repository will have the job type, too.
You’ve noticed the options of other plugins. If your plugin requires the user to configure something, use (or create) the config.html
template and the respective config.js
to save your configuration values.
The plugin template provides the basic structure and also adds the HTML template. By default, both files are located in the plugins config
directory.
You can do all those within the webapp.js
. This is the default file contents. The comments explain the important stuff. We’ll elaborate the properties in more detail.
{
// mongoose schema, if you need project-specific
config: {},
// Define project-specific routes
// all routes created here are namespaced within /:org/:repo/api/:pluginid
// req.project is the current project
// req.accessLevel is the current user’s access level for the project
// 0 - anonymous, 1 - authed, 2 - admin / collaborator
// req.user is the current user
// req.pluginConfig() -> get the config for this plugin
// req.pluginConfig(config, cb(err)) -> set the config for this plugin
routes: function (app, context) {},
// Define global routes
// all routes namespaced within /ext/:pluginid
// req.user is the current user
// req.user.account_level can be used for authorization
// 0 - anonymous, 1 - authed, 2 - admin / collaborator
globalRoutes: function (app, context) {},
// Listen for global events
// all job-local events that begin with `plugin.` are proxied to
// the main strider eventemitter, so you can listen for them here.
// Other events include `job.new`, `job.done` and `browser.update`.
listen: function (emitter, context) {}
}
config
object to define plugin specific properties. E.g. Strider’s GitHub plugin stores the repo url, owner, webhook secret, auth type (SSH or HTTPS), access token, and some more.app
object and declare your routes for e.g. handling incoming web hooks.job.new
, job.done
, browser.update
. Additionally, you can use the provided emitter
object and emit events into Strider’s event loop.We’ve touched the parts on how to add plugin specific configuration options and manipulate Strider’s web UI with your new extension. Now let’s have a deeper look on how to add your plugins functionality to Strider.
The worker.js
file should at least export a function init(config, job, cb)
. Further, you can export a more complext object. The default worker.js
contains the object with init
and autodetect
properties. The code snippet below illustrates the default file.
module.exports = {
// Initialize the plugin for a job
// config: the config for this job, made by extending the DB config
// with any flat-file config
// job: see strider-runner-core for a description of that object
// context: currently only defines „dataDir“
// cb(err, initializedPlugin)
init: function (config, job, context, cb) {
return cb(null, {
// string or list - to be added to the PATH
path: path.join(__dirname, ‚bin‘),
// any extra env variables. Will be available during all phases
env: {},
// Listen for events on the internal job emitter.
// Look at strider-runner-core for an
// enumeration of the events. Emit plugin.[pluginid].myevent to
// communicate things up to the browser or to the webapp.
listen: function (emitter, context) {
},
// For each phase that you want to deal with, provide either a
// shell command [string] for a fn(context, done(err, didrun))
environment: ‚nvm install ‚ + (config.version || ‚0.10‘),
prepare: ‚npm install‘,
test: function (context, done) {
checkSomething(context, function (shouldDoThings) {
if (!shouldDoThings) {
// Send `false` to indicate that we didn’t actually run
// anything. This is so we can warn users when no plugins
// actually do anything during a test run, and avoid false
// positives.
return done(null, false);
}
doThings(function (err) {
done(err, true);
});
});
},
cleanup: ‚rm -rf node_modules‘
});
}
// this is only used if there is _no_ plugin configuration for a
// project. See gumshoe for documentation on detection rules.
autodetect: {
filename: ‚package.json‘,
exists: true
}
};
That’s a lot of content for a fresh start. Let’s review the code parts step by step.
The init
function initializes the functionality of your plugin for a new job. The init
function returns a data object containing various properties (path
), configurations (env
, environment
, prepare
, cleanup
) and functions (listen
, test
).
Strider uses the data object to set up the test environment and executes the defined test function during build phase.
string
command or a function(context, done(err, didrun))
function. You can skip any phase by setting it to undefined (= just remove the respective line from worker.js
) - environment: set up your build environment - prepare: prepare your test run and install required dependencies or modules - test: run your tests - cleanup: clean your room, bro!We’ve reviewed the init
method within default worker.js
file. The second part is an autodetect
object. This object is used by Strider to automatically detect the plugin configuration. Strider uses the strider-detection-rules plugin on top of gumshoe to do the job.
The autodetection process only starts, if there is no plugin configuration and Strider didn’t know how to handle any job using the plugin.
We glitched through the important details of Strider plugins. Remember that every Strider plugin is a also a Node.js app. You can (and should) write tests for your plugin as well. While starting the Strider server, the extension loader searches recursively the node_modules
folder for any plugin and registers the plugin functionality to the platform.
This post is kind of dry and creating new Strider plugins require some research within existing ones. Use the Strider organization on GitHub and dig into various plugins. Have a look at their files to get an impression how they structure files and use configuration options and methods.