Skip to content

Latest commit

 

History

History
258 lines (200 loc) · 7.01 KB

developers.md

File metadata and controls

258 lines (200 loc) · 7.01 KB

Custom Conan Plugins

Creating your own Conan Plugin is very simple, involving only a few steps:

  1. Design The Plugin Interface
  2. Create a Plugin Constructor
  3. Add Components
  4. Add Steps To Components
  5. Create Each Deployment Step
  6. Publish Your Plugin

Basic Conan Plugin Anatomy

While you can use any directory structure you'd like with conan plugins, here is a bare-bones example that we'll use throughout this document:

$ tree my-conan-plugin/
my-conan-plugin/
├── lib
│   ├── component.js
│   └── plugin.js
├── package.json
└── README.md

1. Design The Plugin Interface

Ultimately, your plugin is going to be used in someone's conan.js file (or equivalent), so it's a good idea to begin designing your plugin there.

For this example, we're going to design an interface for logging into a posix-based server on port 8000, then:

  1. Change directory to ~/myApp
  2. Clone a git repository to ~/myApp/releases/${currentDate}
  3. Remove the current soft link to the previous release.
  4. Create a new soft link to the new current release.

conan.js:

const conan = new Conan({});

conan.posixServer("my.staging.server").port(8000);
conan.posixServer("my.production.server").port(8000);

conan.components.posixServer.forEach(server => {
	server
		.changeDirectory("~/myApp/")
		.gitClone("https://github.com/MyCompany/my-conan-plugin.git", "releases/2016-02-10")
		.remove("./current")
		.softLink("releases/2016-02-10", "current");
});

conan.deploy(error => {
	if (error) { throw error; }
	// Deployment is complete
});

2: Create a Plugin Constructor

Conan's plugin system is as simple and un-opinionated as it gets. You start by creating a normal constructor that accepts the current instance of conan as its sole argument. This can be done with classic es5 constructors, or the es6 class keyword as well:

/lib/plugin.js:

// ES5
module.exports = function PosixServerPlugin(conan) {
	this.conan = conan;
}
// ES6
export default class PosixServerPlugin {
	constructor(conan) {
		this.conan = conan;
	}
}

3: Add Components

Components designate parts of a plugin's interface, and the deployment steps to be run. For example, let's setup the posixServer component and add it to conan in plugin.js:

/lib/component.js

import { ConanComponent } from "conan";

class PosixServer extends ConanComponent {
	constructor(hostName, conan) {
		this.conan = conan;

		// Each designated parameter will be given its own
		// getter/setter function on the component instance:
		this.parameters(
			"hostName",
			"port"
		);

		// this.hostName() was created by this.parameters()
		this.hostName(hostName);
	}
}

/lib/plugin.js

import PosixServer from "./component.js";

export default class CustomConanPlugin {
	constructor(conan) {
		// This will create conan.posixServer, which
		// will return an instance of PosixServer
		conan.addComponent("posixServer", PosixServer);
	}
}

4: Add Steps To Components

/lib/component.js

import { ConanComponent } from "conan";

import loginToServer from "./steps/loginToServer.js";
import changeDirectory from "./steps/changeDirectory.js";
import gitClone from "./steps/gitClone.js";
import remove from "./steps/remove.js";
import softLink from "./steps/softLink.js";

class PosixServer extends ConanComponent {
	constructor(hostName, conan) {
		this.conan = conan;

		// Each designated parameter will be given its own
		// getter/setter function on the component instance:
		this.parameters(
			"hostName",
			"port"
		);

		// this.hostName() was created by this.parameters()
		this.hostName(hostName);

		// Add default steps here
		this.conan.steps.add(loginToServer, {
			server: this
		});

		this.changeDirectory();

		this.stepParameters = {
			server: this
		};
	}

	changeDirectory(directoryPath) {
		this.stepParameters.directoryPath = directoryPath;
		this.conan.steps.add(changeDirectory, this.stepParameters);
	}

	gitClone(gitRepoUri, localDirectoryPath) {
		this.stepParameters.gitRepoUri = gitRepoUri;
		this.stepParameters.localDirectoryPath = localDirectoryPath;
		this.conan.steps.add(gitClone, this.stepParameters);
	}

	remove(filePath) {
		this.stepParameters.filePath = filePath;
		this.conan.steps.add(remove, this.stepParameters);
	}

	softLink(fromFilePath, toFilePath) {
		this.stepParameters.fromFilePath = fromFilePath;
		this.stepParameters.toFilePath = toFilePath;
		this.conan.steps.add(softLink, this.stepParameters);
	}
}

5: Create Each Deployment Step

Each step is a single function that automatically receives exactly three arguments:

  • conan - This is the instance of conan you're using.
  • context - An object with three properties:
    • context.parameters - The parameters sent in by your plugin.
    • context.libraries - Libraries you can setup to be available to every step.
    • context.results - An aggregate of each result value passed back by each step.
  • stepDone - The callback for when the step has completed. Accepts an error as the first argument, and an object as the second which is aggregated into context.results;

For example, here is the complete step for the above example's loginToServer step:

./lib/steps/loginToServer.js:

import SSH from "simple-ssh";

export default function loginToServer(conan, context, stepDone) {
	const server = context.parameters.server;

	const hostName = server.hostName();
	const userName = server.username();
	const password = server.password();
	const port = server.port();

	const ssh = new SSH({
		host: hostName,
		user: userName,
		pass: password,
		port: port
	}).on("ready", () => {
		stepDone(null, {
			ssh: ssh
		});
	});
}

Note: Any property you set on the return value object of stepDone will be aggregated onto context.results, and any duplicate values set will overwrite the previous.

6: Publish Your Plugin

After your steps and components are completed, you'll want to publish your plugin some place where others can find it. To do this, just add the keyword conan-plugin to your package.json file and publish as you would normally to npm. Voila! Your plugin is published and ready to be shared with others!

{
  "name": "my-conan-plugin",
  "version": "0.0.1",
  "description": "Deploy to posix-based web servers with ease!",
  "main": "lib/plugin.js",
  "scripts": {},
  "repository": {
    "type": "git",
    "url": "https://github.com/MyCompany/my-conan-plugin.git"
  },
  "keywords": [
    "conan-plugin",
		"posix",
		"server",
		"deploy"
  ],
  "author": "My Company, LLC",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/MyCompany/my-conan-plugin/issues"
  },
  "homepage": "https://github.com/MyCompany/my-conan-plugin",
  "dependencies": {},
  "devDependencies": { }
}