Welcome to GRU’s documentation!¶
What is GRU?¶
GRU is a host-centric inventory management system. It is used to visualize and provide context on individual servers and more importantly, on groups of servers.
It was designed in order to help operations teams, as well as developers to better understand their infrastructure and to provide a unified view of available compute resources.
This is what it looks like:

GRU Host breakdown by role

GRU Host info page
Motivation¶
Understanding which compute resources exist (and which business context they serve) can be tricky. In many cases, this information is scattered across several systems.
GRU is designed to give an accurate view of compute resources available to us. The main primitive in GRU is the Host. By providing different ways to slice and dice an inventory (or, collection of hosts) and allowing plugins to give external context regarding each host, we can better discover, understand and optimize our infrastructure.
Plugins, that are at the center of GRU, are used to integrate information and actions from external systems. These can include monitoring, CMDB, real time metrics, cost info, service checks and more. By uniting all these into one place we can easily reason about our resources and provide visibility to operations and software engineers.
Contents:
Quick Start¶
Installation¶
There are two ways to get started with GRU: Either clone the project and run the python server - or run it using the the Docker image.
We recommend using Docker, since it eliminates the need to install all the different python and OS dependencies.
If you plan on contributing or simply hacking on GRU itself, you should probably use the local server instead. Keep in mind that GRU provides a pretty flexible plugin system. You can develop your own plugins and keep using the docker image.
Basic Configuration¶
Before we can run GRU, we need to setup a yaml file describing how to access our inventory and optionally how to authenticate and authorize users as well as set other configuration parameters to adjust GRU to our liking.
Here’s a very basic example of how this configuration might look like, without any authentication or authorization:
---
flask:
# Keep this one random. used by the session system.
secret_key: 'fojegj340fuccotnhvi39yombpris'
# Here we set up EC2 as our source of inventory.
inventory:
provider: gru.contrib.inventory.providers.EC2Provider
config:
accounts:
# setting aws_access_key and aws_secret_access_key is optional
# otherwise, will be taken from ~/.aws/credentials
# or environment variables
- regions: ['us-east-1']
# Whiche metadata field to use as a hostname
host_display_name_field: tag:Name
# Fields to create logical groups of hosts by
group_by:
- field: tag:stack
title: Stack
- field: tag:role
title: Role
- field: region
title: Region
# Fields visible when showing a table of hosts
host_table_fields:
- field: tag:role
title: Role
- field: tag:environment
title: Env
- field: architecture
title: Arch
- field: instance-state-name
title: Status
- field: private-ip-address
title: IP
# Fields visible by default when looking at a single host
# (The user can toggle to view remaining fields)
host_info_fields:
- field: tag:role
title: Role
- field: region
title: DC
- field: tag:environment
title: Env
- field: architecture
title: Arch
- field: instance-state-name
title: Status
- field: private-ip-address
title: IP
Please review the Configuration Reference section or check out the examples/
directory on Github.
Running using Docker¶
Assuming you already have a yaml configuration file named gru.yaml
, all we need to do is run the docker image:
$ docker run --rm -it \
-v $PWD:/etc/gru \
-e GRU_SETTINGS=/etc/gru/gru.yaml \
-e "AWS_ACCESS_KEY_ID=AKXXXXXXXXXXXXXXXXXX" \ # Set this to your AWS keys
-e "AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-p 5000:5000 \
similarweb/gru:latest
If you are using custom plugins, simply mount the module directory and reflect that in your settings file:
$ docker run --rm -it \
-v $PWD:/etc/gru \
-e GRU_SETTINGS=/etc/gru/gru.yaml \
-e "AWS_ACCESS_KEY_ID=AKXXXXXXXXXXXXXXXXXX" \ # Set this to your AWS keys
-e "AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-v /path/to/plugins:/opt/gru-plugins \
-p 5000:5000 \
similarweb/gru:latest
This will automatically pull the gru image from docker hub, and run a server using the gru.yaml
settings file.
Running a local server¶
Running a local server has been tested on Debian and Ubuntu linux. It should be able to run on OSX and Windows as well, but it hasn’t been thoroughly tested.
First, install GRU’s dependencies:
$ apt-get install --no-install-recommends \
python-dev \
libssl-dev \
libsasl2-dev \
libldap2-dev
Once that’s done, we need to clone the GRU project:
$ git clone git://github.com/similarweb/gru .
Then, install all the required Python dependencies. Preferably, this step should be done in a virtualenv):
$ pip install -r requirements.txt
Now, run the server itself using the settings file you created:
$ GRU_SETTINGS="/path/to/gru.yaml" python app.py runserver
A server will be started, listening on http://localhost:5000
Inventory Providers¶
GRU supports pluggable inventory providers. Inventory providers fill in the list of hosts that make up GRU’s view of the world. They allow grouping, listing and searching for hosts, as well as provide metadata for every host in the inventory.
Out of the box, GRU provides two different inventory providers: an ElasticSearch provider that assumes hosts appear as documents in an ElasticSearch cluster; and an EC2 provider that queries the AWS EC2 API
Configuring an Inventory Provider¶
at the most basic level, you need to set the inventory.provider configuration parameter to a fully qualified class name of a class that implements an inventory provider.
ElasticSearch Provider¶
The ElasticSearch provider assumes that you have configured an ElasticSearch cluster with an index containing hosts as documents. The document IDs in the index correlate to host IDs, and the documents are JSON encoded dictionaries of host metadata.
It’s actually pretty simple to create such an index. See the provided mapping file and an example script that populates the hosts index
To enable the ElasticSearch provider, please use gru.contrib.inventory.providers.ElasticSearchProvider
as the value for inventory.provider
and refer to the provider documentation.
Here’s a snippet of such a configuration:
---
inventory:
provider: gru.contrib.inventory.providers.ElasticSearchProvider
config:
index: gru-inventory
timeout_seconds: 10
hosts:
# HTTP endpoints to your elasticsearch clusters.
# No need to specify all servers, they are used for discovery
- http://10.0.0.1:9200
- http://10.0.0.2:9200
...
EC2 Provider¶
To enable the EC2 provider, please use gru.contrib.inventory.providers.EC2Proivder
as the value for inventory.provider
and refer to the provider documentation.
The EC2 provider supports aggregation of hosts from several different AWS accounts and/or regions.
Here’s a snippet of such a configuration:
---
provider: gru.contrib.inventory.providers.EC2Provider
config:
accounts:
- aws_access_key_id: AKXXXXXXXXXXXXXXXXXX
aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
regions: ['us-east-1', 'us-west-2']
Writing your own¶
Writing an inventory provider is covered under Writing your own inventory providers
Authentication and Authorization¶
GRU supports pluggable authentication and authorization.
Authorization¶
To make things simple, authorization is done via user groups: a plugin can configure an allowed_groups
variable
that lists group names that have authorization to use the plugin.
Configuring an Authentication Backend¶
Configuring the Authentication backend is done via the authentication.backend
and authentication.config
configuration parameters.
Both are covered in the configuration documentation
Dummy authentication backend¶
By default, gru sets the authentication backend to gru.contrib.auth.backends.DummyBackend
.
This backend, as its name suggests, does nothing. No need for users to login, and users have no groups associated with them.
If you use GRU in a network where only authorized personnel have access to the web interface, this might be enough. Make sure you protect this network and that you don’t accidentally expose it to the world.
For all other situations, it is recommended to use a real authentication backend such as the LDAP one described below.
LDAP authentication backend¶
The LDAP backend is used to authenticate users against an existing LDAP server (such as Active Directory or OpenLDAP).
Once a user has been authenticated, the LDAP backend also populates user’s group names, to allow plugins to authorize only users of specific groups.
To enable the LDAP backend, please use gru.contrib.auth.backends.LdapBackend
as the value for authentication.backend
and refer to the backend documentation for details on how to populate the authentication.config
dictionary for the LDAP backend.
Here’s a snippet of such a configuration:
---
authentication:
backend: gru.contrib.auth.backends.LdapBackend
config:
server: 10.0.0.7
port: 3268
bind_user: 'CN=gru,OU=Internal Tools,DC=mycompany,DC=com'
bind_password: '**************'
user_query: '(sAMAccountName=$username)'
Writing your own¶
Writing an authentication and authorization backends is covered under Writing your own authentication backends
Using plugins¶
Gru was built with the concept of extendability in mind. Since an inventory of hosts can have many different usages and different companies have a different view of the world (also with regards to how their infrastructure is managed), there’s built-in support for 2 main types of plugins: Host widgets and Page plugins
Host Widgets¶
Host Widgets are plugins that live within the context of a single host.
They are generally used to provide additional information about the host from external systems (say monitoring, configuration management, cost, etc) or to support actions to be carried out for that host.
Page plugins¶
Page plugins are plugins that transcend hosts in general. As their name suggests, they are pages that are rendered within GRU and have access to the host inventory system. They are generally designed to complement GRU’s use-cases by adding more host/fleet related functionality.
For example, at SimilarWeb, we built a page plugin that displays currently open incidents, linking back to relevant stacks and servers - which is used by on-call personnel.
Loading plugins¶
In order to load an existing plugin, you’ll need to do the following:
Make sure the plugin’s python module is in gru’s
sys.path
(python’s search path for modules).The easiest way to accomplish this, is to either place the plugins under the default plugins.directories.
Alternatively, you can specify your own directories to override the default paths.
For plugins that require configuration, see their documentation and update your GRU settings YAML file accordingly.
Restart the GRU web server for the changes to get picked up.
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.
-
Configuration Reference¶
Setting up a settings file¶
GRU is configured using a YAML settings file. see the Quick Start guide to see how make sure GRU picks up and uses the settings file we’ll be providing.
At the most basic level, we need to let GRU know of the following:
- Which inventory provider we want to use. EC2 and ElasticSearch come out of the box, but you can write your own.
- Which authentication backend you want to use. Dummy (no auth at all) and LDAP authentication are provided, but, again, feel free to write your own.
- How you want to display your inventory: Which metadata should be visible and where
- Which external plugins you want to load, including any configuration they may require
Below is the full list of configuration parameters that GRU supports, including all the builtin plugins.
You can check out the examples/
directory on Github for complete examples.
All basic configuration parameters¶
For brevity, nested keys will be denoted using a period (.
) i.e.:
foo.bar = <some value>
Is equivalent to the following YAML structure:
---
foo:
bar: <some value>
flask.debug
¶
boolean whether to start the underlying flask server in debug mode or not. This will affect the logging verbosity
flask.secret_key
¶
string used to encrypt signed cookies, used by the session system. Any random value will do.
flask.session_seconds
¶
integer how long in seconds to keep user sessions.
Defaults to 604800
(7 days)
plugins.directories
¶
array of strings list of directories that contain GRU plugins.
Defaults to ['~/.gru-plugins', '/opt/gru-plugins']
See Loading plugins for more info
plugins.modules
¶
array of strings list of module names to import and search for plugins
See Loading plugins for more info
authentication.backend
¶
string class name for the authentication backend to use.
Out of the box, you can use either gru.contrib.auth.backends.LdapBackend
or gru.contrib.auth.backends.DummyBackend
.
inventory.provider
¶
string class name of the inventory provider to use.
Out of the box, you can use either gru.contrib.inventory.providers.ElasticSearchProvider
or gru.contrib.inventory.providers.EC2Provider
.
inventory.group_by
¶
array of dictionaries
Describes the list of Host attributes to group the inventory by. This is used by the host breakdown screens. Common fields to group hosts by may include: data center (or AWS region), environment, role and operating system.
Each dictionary should provide at least the field
key, which corresponds to a Host attribute name, and an optional
title
key which will be used by the UI to give this field a human readable name.
Example:
---
inventory:
group_by:
- field: os
title: Operating System
- field: dc
title: Data Center
- field: role
...
This will appear in the UI under the “Browse Inventory” drop-down.
inventory.host_display_name_field
¶
string
A name for a Host attribute that will be used as the host’s display name in various places in the UI.
Examples: "hostname"
, "instance-id"
inventory.host_table_sort_by
¶
string
A name for a Host attribute that will be used when sorting lists of hosts, on supporting Inventory Providers. Out of the box, this is currently only supported by the ElasticSearch provider.
Examples: "hostname"
, "instance-id"
inventory.host_table_fields
¶
array of dictionaries
Describe the list of Host attributes used when showing a table of multiple hosts. Common fields will generally include: role, data center, OS, IP address, # of cores, memory GBs, etc.
Each dictionary should provide at least the field
key, which corresponds to a Host attribute name, and an optional
title
key which will be used by the UI to give this field a human readable name.
Example:
---
inventory:
host_table_fields:
- field: os
title: Operating System
- field: dc
title: Data Center
- field: role
- field: num_cores
title: "# Of Cores"
- field: memory_gb
title: Memory GB
- field: ipaddress
title: IP address
...
The field names may vary depending on your Inventory Provider.
inventory.host_info_fields
¶
array of dictionaries
Describe the list of Host attributes used when a single host. Other fields will be hidden behind a “show more...” button. Common fields will generally include: role, data center, OS, IP address, # of cores, memory GBs, etc.
Each dictionary should provide at least the field
key, which corresponds to a Host attribute name, and an optional
title
key which will be used by the UI to give this field a human readable name.
Example:
---
inventory:
host_info_fields:
- field: os
title: Operating System
- field: dc
title: Data Center
- field: role
- field: num_cores
title: "# Of Cores"
- field: memory_gb
title: Memory GB
- field: ipaddress
title: IP address
...
The field names may vary depending on your Inventory Provider.
LDAP authentication backend configuration parameters¶
Here’s what an LDAP configuration might look like:
authentication:
backend: gru.contrib.auth.backends.LdapBackend
config:
server: 10.10.10.10
port: 3268
bind_user: 'CN=gru_bind_user,OU=ops,DC=example,DC=com'
bind_password: 'binduserpassword'
user_query: '(sAMAccountName=$username)'
authentication.config.user_query
¶
string
LDAP query to perform when searching the logging in user.
You can use the $username
interpolation token which will be replaced by the value provided by the logging in user.
Configuration Parameters for the built-in ElasticSearch inventory provider¶
inventory.config.hosts
¶
array of strings
A list of urls to use when connecting to the ElasticSearch cluster.
inventory.config.timeout_seconds
¶
integer
Amount of time in seconds before timing out a request to an ElasticSearch query
Defaults to 30
.
Configuration Parameters for the built-in EC2 inventory provider¶
inventory.config.connections
¶
array of dictionaries
Every connection to the EC2 API is represented by an entry in the connections array.
For each connection, specify the following:
inventory.config.accounts
- array the AWS accounts we want to connect to.
inventory.config.accounts.[].aws_access_key_id
- string optional the AWS access key ID to connect with. If omitted, it will be searched in your filesystem and environment variables in the order listed in boto’s documentation
inventory.config.accounts.[].aws_secret_access_key
- string optional the AWS secret access key to connect with. If omitted, it will be searched in your filesystem and environment variables in the order listed in boto’s documentation
inventory.config.accounts.[].regions
- array of string optional The AWS regions we wish to connect and pull inventory from. Example: ['us-east-1', 'us-west-2']
Example:
---
provider: gru.contrib.inventory.providers.EC2Provider
config:
accounts:
- aws_access_key_id: AKXXXXXXXXXXXXXXXXXX
aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- regions: ['us-east-1', 'us-west-2']
Contributing¶
How to file an issue¶
To file an issue, please go to the Github Issues page.
Make sure you follow these guidelines:
- Give as much context as you can. Please describe:
- The flow of actions leading to the issue
- The GRU version used
- The Python version used
- Docker version used, if applicable
- Operating system and version
- Links to all installed open source plugins
- Your gru.yaml configuration file
- Be polite. GRU is an open source project. People choose to contribute to it out of their desire to better our community and tools. Please treat them with respect and kindness.
- Make sure your issue hasn’t been filed yet. It is likely that someone else has already encountered the same problem as you. Search the open and recently closed issues for ones similar to yours.
How to contribute¶
GRU is an open source project and contributions are highly encouraged!
In order to work together productively, please follow these contribution guidelines:
- Before submitting a pull request with a new feature, please open an issue, tagging it with
Feature Request
. This is a community driven project and there will likely be some discussion on what the optimal solution is. This discussion might affect how and whether this feature should exist. - Make sure your code submission respects the coding style of its surrounding code. As a rule of thumb for new files, make sure you respect PEP8.
- If code changes any documented behaviour, make sure the documentation is updated as well. We will not merge pull requests that end up breaking current documentation.
Road Map¶
This is GRU’s roadmap for the upcoming release:
0.2.0¶
- Release GRU as open source!
- Improve the EC2 inventory provider
- Parallelize requests to different regions/accounts
- Cache calls to AWS API for a configurable retention
- Add proper pagination support to lists and search results
- Document the search syntax better
- Improve documentation
- Add Unit tests for plugin loader and host pages
- TBD - we’d love your input.