Developing Plugins¶
To understand the basic concept of plugins and how to use them, refer to the usage documentation
This document describes the process and APIs required to develop your own plugins.
Plugin Architecture Overview¶
GRU Plugins are Python classes that subclass abstract plugin classes.
For example, to write your own Host Widget, you’ll need to subclass the gru.plugins.base.hostwidget.HostWidgetPlugin
class.
On startup, the server will do the following:
- Add the directories listed under plugins.directories to Python’s
sys.path
- It will then iterate over the module names listed in
plugins.modules
and import them - For each imported module, the server will iterate over all classes, looking for ones that subclass any of GRU’s internal abstract plugin types
- An object will be instantiated out of each class found. The default
on_load()
method will be called. This is where plugin startup code should live. Things like setting up database connections should be done there - For modules that expose views, a Flask Blueprint will be registered and used when serving these views
All plugins subclass the following abstract class:
-
class
gru.plugins.base.
BasePlugin
¶ -
on_load(self):
Defaults to doing nothing. If you have any startup logic, this is the place to put it.
-
Host Widgets¶
To write your own Host widget you’ll need to provide the following:
A subclass of
gru.plugins.base.hostwidget.HostWidgetPlugin
- A template file that extends
plugins/host_widget.html
. This Jinja template defines 3 parts that you’ll be able to overide. They will later be merged into the host info page. These parts are: plugincss
- will be loaded at the head of the host info page. This is where<style>
or<link rel="...">
tags should go.pluginhtml
- the actual HTML to be rendered inside the host info page body, under the basic host informationpluginjs
- will be placed at the bottom of the HTML body. This is where<script>
tags should go.
All template blocks will get the results of the plugin class’
get_context(self, host)
method. That’s probably the one you’ll need to override.- A template file that extends
Here’s a reference of the plugin class:
-
class
gru.plugins.base.hostwidget.
HostWidgetPlugin
¶ -
template_name
¶ A name of a template file to render. The file must extend
plugins/host_widget.html
and define the blocks listed above.You can define a
templates/
directory inside your module’s parent directory. If it exists, GRU will add it to the template search path.
-
get_title(self):
You must provide an implementation of this method. It should return a string that will be used as the title for this widget in the host info page.
-
qualify(self, host):
Given a
gru.plugins.inventory.Host
object, should returnTrue
orFalse
. IfTrue
, the widget will be rendered for the given host’s info page.This is useful If you have widgets that don’t make sense for every host, so you want to display them only where appropriate.
-
def get_context(self, host):
Given a
gru.plugins.inventory.Host
object, should return a python dictionary to be used as context for the template file. The default implementation simply returns{}
, so you’ll likely want to override it.
-
def ajax_request(self, request):
Useful if you want the widget to be able to send AJAX requests back to the server. The method will receive a Flask request object, and should return a valid Flask response.
-
Page Plugins¶
To write your own Page Plugin you’ll need to provide the following:
- A subclass of
gru.plugins.base.page.PagePlugin
- Optionally, if this page plugin returns an HTML page to be rendered within GRU: A template file that extends
plugins/page.html
. This Jinja template defines 3 parts that you’ll be able to overide. They will later be merged into the host info page. These parts are: plugincss
- will be loaded at the head of the page. This is where<style>
or<link rel="...">
tags should go.pluginhtml
- the actual HTML to be rendered. GRU will wrap this with the default navbar, footer, etc.pluginjs
- will be placed at the bottom of the HTML body. This is where<script>
tags should go.
- Optionally, if this page plugin returns an HTML page to be rendered within GRU: A template file that extends
- If the plugin defines a
get_title()
method, it will be linked to from the navigation bar, in theplugins
dropdown.
Here’s a reference of the plugin class:
-
class
gru.plugins.base.page.
PagePlugin
¶ -
template_name
¶ Optional - a name of a template file to render. The file must extend
plugins/page.html
and define the blocks listed above.You can define a
templates/
directory inside your module’s parent directory. If it exists, GRU will add it to the template search path.You may also define a
static/
directory, containing static resources such as images, scripts and CSS files. To later include them within your template, use theplugin_static()
function within your template. Example:<script src="{{plugin_static('myplugin/js/plugin.js')}}"></script>
This will be replaced with the URL given to your
static/myplugin/js/plugin.js
file.
-
get_title(self):
Should return a string. It will be used to register the plugin in the plugins dropdown, in GRU’s navigation bar. The default implementation returns
None
meaning it won’t be registered in the plugins dropdown.
-
handler(self, request):
The method will receive a Flask request object, and should return a valid Flask response. You may simply
return self.render()
here, which will render the template you provided using thetemplate_name
attribute.Sometimes it’s useful to register a PagePlugin which returns a JSON response. Generally it will be called using an AJAX call by another plugin. In such a case, you should keep the default
get_title(self)
implementation and probably leavetemplate_name
blank as well.
-
render(self, http_status=200, **kwargs):
A helper method that renders a Jinja template and return a Flask response. Whatever you passed in as
kwargs
will be added to the Jinja context when rendering the template.The template to render will be taken from the
template_name
attribute.
-
Inventory Providers¶
Writing an Inventory Provider requires subclassing gru.plugins.inventory.InventoryProvider
.
To use the plugin you developed, you’ll need to make sure the module is loaded (specified under plugins.modules
) and
that inventory.provider
points to your subclass’ path.
-
class
gru.plugins.inventory.
InventoryProvider
¶ -
host_group_breakdown(self, category):
Returns a list of
gru.plugins.inventory.HostCategory
objects. Example: ifcategory = "datacenter"
, an expected return value would be[HostCategory("datacenter", "us-east-1", 10), HostCategory("datacenter", "us-west-2", 5)]
-
list(self, category, group, sort_by=None, from_ind=None, to_ind=None):
Returns a
gru.plugins.inventory.HostList
object after filter by a field and value. Example:provider.list("datacenter", "us-east-1")
will return all Hosts in the us-east-1 datacenterUse
from_ind
and to_ind`` to support pagination. Both values are zero-based integers.sort_by
is optional. If set, the list ofgru.plugins.inventory.Host
objects should be sorted by this field name.
-
host_search(self, query, from_ind=None, to_ind=None):
Given a query string, perform a search of hosts and returns a
gru.plugins.inventory.HostList
object.query
will be a string provided by the user in the search box.Use
from_ind
and to_ind`` to support pagination. Both values are zero-based integers.
-
get_host_by_id(self, host_id):
Return a
gru.plugins.inventory.Host
object for the provided host_id.
-
Host, HostCategory and HostList¶
-
class
gru.plugins.inventory.
Host
¶ A
Host
is the most basic primitive in GRU. A host will generally have some unique ID (a MAC address, an AWS instance ID, or similar) and a key/value mapping of metadata. Common metadata attributes may include OS version, IP address, role, datacenter, etc.-
__init__(self, host_id, host_data=None):
Creates a new host by passing in a host identifier (should be unique) and a python dictionary describing host metadata information.
-
field(self, field_name, default=None):
Returns a field value from the host metadata.
Also supports fetching nested fields using
"."
notation. i.e.host.field('os.name')
will work with this metadata:{'os': {'name': 'Linux'}
-
-
class
gru.plugins.inventory.
HostCategory
¶ Use this class when returning a list of host categories from the
host_group_breakdown()
method of agru.plugins.inventory.InventoryProvider
.-
__init__(self, category, group, count=0):
category
- a string specifying the category to breakdown by. e.g."datacenter"
group
- a string specifying the current group. e.g."us-east-1"
count
- an integer specifying the amount of hosts in the group. e.g.0
-
-
class
gru.plugins.inventory.
HostList
¶ This class represents a response to a search query or filter by an
gru.plugins.inventory.InventoryProvider
.-
__init__(self, hosts=None, total_hosts=0):
hosts
-a list ofgru.plugins.inventory.Host
objects.total_hosts
- integer. If the number passsed is bigger thanlen(hosts)
, a paginator will appear.
-
Authentication Backends¶
Writing an Authentication Backend requires subclassing gru.plugins.auth.AuthenticationBackend
.
To use the plugin you developed, you’ll need to make sure the module is loaded (specified under plugins.modules
) and
that authentication.backend
points to your subclass’ path.
-
class
gru.plugins.auth.
AuthenticationBackend
¶ -
authenticate(self, username, password):
Given a username and a password, should return a
gru.plugins.auth.User
object if authentication is successful.Otherwise, return
None
.This is the only method an authentication backend has to implement.
-
member_of(self):
Will check which memebers the currently logged in user is a member of. If not logged in, will return an empty list.
-
is_logged_in(self):
Returns True if there’s a user currently logged in.
-
login(self, user):
Stores the provided user object as part of the HTTP session
-
logout(self):
Clears the current session.
-
get_forbidden_url(self):
Returns a URL for a “Forbidden” page, in case a users tries a forbidden action
-
get_login_url(self, path):
Returns a URL for the Login form page. If
path
is provided, it will be passed to the login page, which will redirect to it upon successful login.
-
The User object¶
-
class
gru.plugins.auth.
User
¶ Represents a logged in user. Has a username, real name, an optional list of groups and optional metadata
-
__init__(self, username, name, groups=None, user_data=None):
Create a new user object. It will be serialized and stored as part of the session.
username
- The username used during authentication.name
- The user’s full name.groups
- A list of strings describing the groups this user is a member of. Optional.user_data
- A dictionary of additional metadata we want to store about this user. Optional.
-