Empire now has a new feature: plugins! These allow for a great deal of flexibility and customization if you’re willing to write a little bit of code, and this post is dedicated to helping you get the most out of this new way Empire can be customized to meet your needs.
The start of a plugin system was added in pull request #790 and will be coming soon to an Empire near you. This article will walk through:
How to Use Empire Plugins
You’ll notice the Empire help menu has two new commands;
plugins command will give you a list of all the plugins you have available:
In order to make a plugin available to load, it needs to be present in the
plugins directory - if you’re not sure where to find that, the
plugins command prints it out.
Once you’ve installed a plugin (by placing it in the
plugins directory), you can load it by running
plugin $plugin-name, like you see below:
Plugins are unloaded whenever Empire exits. Right now, that’s the only way to unload them.
Once your plugin is loaded, running the
plugins command will show which plugins are active by putting a row of
* characters under the “active” heading in that plugin’s row:
This particular plugin gives us one new capability - a command avilable from the main menu called
test. Running the command gives you some example output:
Obviously this command isn’t very useful. :)
That’s it for how to use plugins - next up, how do they actually work?
How Empire Plugins Work
Note: all links to code in this section are to the original commit in Github which added plugin functionality, and therefore will not reflect more recent changes.
The core of the plugin system is in two commands added to the main Empire menu (both of which are described above). The plugins command lists all available plugins, and notes which ones are loaded. It does this by calling
pkgutil.walk_packages on the
plugins directory, which recursively yields all modules in that directory, and comparing their names against a list of loaded plugins stored in
self here referring to the Empire mainMenu object). The plugin command, meanwhile, starts off the same by using
pkgutil.walk_packages, but instead of printing all of the modules it finds, it merely checks to make sure that the plugin name passed to it exists in the list of plugins it finds. If the plugin named by the user actually exists, it calls plugins.load_plugin, which does a series of things:
- Take the name of a plugin and turn it into a fully-named python import (e.g. going from
- Carry out the actual import using importlib.import_module - any code written in the body of the plugin module outside the
Pluginclass will execute here.
- Instantiate the
Pluginclass found within the newly-imported module. (see the parent class for that object and the “How to Write a Plugin for Empire” section below for what this class looks like) Any code in the
onLoadfunction of the
Pluginclass will execute here.
- Reference that plugin object in
mainMenu.loadedPlugins[pluginName]so it can be accessed later by any code with access to the
That’s fundamentally it - from here, it matters what the plugin itself does. The plugin object is in charge of doing any setup work for itself, such as adding new commands to the Empire menu - we’ll dive into how to do that next.
How to Write a Plugin for Empire
A simple example plugin ships with Empire. Let’s figure out how it works.
To start, we’ll go over the basics: any plugin written for Empire MUST have a class named
Plugin, which inherits from
lib.common.plugins.Plugin. Working within that structure, almost anything is possible!
When the plugin is first loaded into Empire, any code you write outside the
Plugin class will execute - for instance, the example plugin has
print("Hello from your new plugin!"), which will run immediately when the user loads the plugin. Note that any code you put here will only run when the module is imported, not when a new plugin instance is created (which, right now, is a useless distinction since each thing is done once, but it’s unclear if that will ever change - see the plugin loading process in “How Empire Plugins Work” above).
Whenever a new instance of your
Plugin object is created, the
onLoad member function will be executed to allow your plugin to do any setup work it needs to do. In the example plugin, this simply prints another welcome message and sets up a variable to be used later -
self.calledTimes, which will persist until the plugin is unloaded (e.g. Empire exits).
onLoad function is done, a critical function is called:
register. This function is passed an instance of the
mainMenu object, which is a core Empire component. Manipulating
mainMenu is what allows you to add functionality to Empire. In the example plugin, we add a new command -
test. This is done by defining a function called
do_test (the command name is taken automatically from the name of the function) and adding it to the
mainMenu.__class__ object. This modifies the class of the
mainMenu object, mutating it to include your new command!
From there, it’s simply a matter of writing the custom functionality you want to add - you can import whatever Empire modules and packages you should need (the example imports a helper which prints output in color) to lighten the burden along the way.