Hacking the gnome itself

digraph d {
   node[shape=rectangle]

   subgraph cluster_codebase {
      label="codebase";
      app [label="app.py\n(process callbacks)"];
      util [label="util.py\n(abstraction)"];
      policy [label="policies/__init__.py\n(register plugins)"];
      plugins [label="plugins/*\n(do interesting things)"];
      app -> util -> policy -> plugins;
   }
   GitHub
   callback [shape=ellipse]
   ghapi [shape=ellipse label="GitHub\nAPI v3"]
   callback -> GitHub [dir=back]
   app -> callback [dir=back];
   util -> ghapi;
   GitHub -> ghapi [dir=back];
   anything [shape=ellipse];
   plugins [shape="folder"];
   plugins -> anything;
   plugins -> ghapi;
   humans -> GitHub;
}

Codebase

The callback service is provided by a Flask app, and the code for this is in gnome/app.py. It’s only job is to receive callbacks from GitHub and process them.

app.py delegates the interesting stuff to code in gnome/utils.py. This does two things, interacts with GitHub to obtain configuration, then delegates configured tasks to the plugins (via the plugin register, gnome/policies/__init__.py)

Ultimately, interesting stuff is delegated to plugins. All plugins must provide a dispatch_gnome() method. If configured, this is called with data from the originating callback event (and config).

app.py

Flask API that processes callback messages from GitHub (or localhost). Messages are validated, then dispatched to all configured policies.

gnome.app.index()[source]

gnome/utils.py

class util.CallbackEvent(request)[source]

CallbackEvent is an abstraction over the raw flask request object. It provides convenience methods for validation and payload access.

headers()[source]
is_valid()[source]
payload()[source]
class util.Config(callback)[source]

Config is generated from the .gnome.yml file that is found in the root of a repository that is a source of GitHub callback events.

The .gnome.yml file is retrieved, parsed and validated. Then, the get_activities() method can be used to instantiate policy objects for everything that was configured in the repo.

get_activities()[source]

This is the magic method. It processes the config (from .gnome.yml) and instantiates the policies, which are presumably dispatched.

get_yaml()[source]
yaml_is_valid()[source]
exception util.InvalidPayloadJSONError[source]

gnome/gh.py

class gh.EventSourceValidator[source]

GitHub publishes the address ranges that they make callbacks from.

Instances of this class can be used to validate ip addresses, like a kind of dymanic whitelist.

get_hook_blocks()[source]

Fetch the whitelisted addresses blocks published by GitHub (directly, or from cache).

ip_str_is_valid(ip_str)[source]

This function returns true if the IP address (string) passed to it is within the address blocks published by GitHub.

class gh.Issue(repo, gh_issue)[source]

Wrapper of pygithub.Issue.Issue, with cache and convenience methods.

has_milestone()[source]
move_to_milestone(new_milestone)[source]
class gh.Milestone(repo, milestone)[source]

Wrapper of pygithub.Milestone.Milestone with cache and convenience methods. Bound to a Repo instance for access to the Github connection (credentials etc).

description
due_on
number
open_tickets()[source]
title
update(**kwargs)[source]
class gh.Repo(repo_name)[source]

This class is an abstraction over the GitHub repository.

It interacts with GitHub as the configured user. Note this is a double-stacked abstraction (it’s a wrapper around the PyGithub library, which wraps the GitHub API v3). That makes the code seem a little strange on first reading, however it simplifies mocking in tests at the business logic layer.

create_milestone(milestone_name, state='open', description=None, due_on=None)[source]

If the milestone does not exist, create it.

If (optional) date passed in, set that date on the milestone. Dito for description. Otherwise, both empty.

Returns the created (or pre-existing) Milestone instance.

ensure_milestone_exists(milestone_name, description=None, date=None)[source]

If the repo does not have a milestone with the given name then create one.

If description or date parameters are provided, and the milestone is created, then they will be used.

If the milestone already exists, and the date or description differ from the ones provided, they will be ignored. This is NOT follow the “upsert” pattern.

ensure_milestone_has_due_date(milestone_name, due_date)[source]

If the milestone does not have a due date, or if it has a due date that differs from the one provided, then update the due date to the one provided.

get_config()[source]
get_milestone(milestone_name, cache=True)[source]

Returns the milestone with by name (or None)

milestone_exists(milestone_name)[source]

Returns True if the milestone exists.

milestones
update_milestones()[source]
upsert_milestone(title, **kwargs)[source]
gh.repo_from_callback(callback)[source]

gnome/policies/__init__.py

The util module instantiates the policy module. This is a very simple thing, all it does is import the relevant classes (from modules in the plugins directory).

When you make a new plugin, it won’t do anything until you register it by importing the relevant class into policies/__int__.py

Browse from there the plugins (see next section, Hacking Policies)…

Tests