spinbot
A GitHub bot for managing Spinnaker's repos.
Config
The production config is checked in as config/config.yaml.
Certain properties are exposed using command-line flags, such as
--events.enabled=False. These are parsed/delimited by . and merged with the
contents of ~/.spinbot/config.
Running for development
You will need the following:
- python3
- pip
- make -- largely optional, you can read
theMakefilein this repo to see what it executes (it's just a few, small
commands).
- Install the necessary packages:
make init-
Edit the config file to tell the bot to use local storage:
storage: local: path: ~/.spinbot/cacheThen create that file:
mkdir ~/.spinbot && touch ~/.spinbot/cache -
Create a github token and add it to the config:
github: token_path: ~/.spinbot/github_token -
Run
./spinbot.py. It will start up a web server. Trigger a run by
sending aPOSTrequest:$ curl -X POST http://localhost:8080 -o /dev/null
If you want to build the Docker container, either rely on the Dockerfile in
the root of the repository, or run:
make dockerThis assumes you have build access in the spinnaker-community GCP project
-- you can edit PROJECT variable in the Makefile to change this.
Deploying to Production
Upon merge, a build will be triggered in the spinnaker-community Google Cloud
Build project.
This build will deploy the latest version to production.
It runs as a Google Cloud Run service.
How it works
To help manage the Spinnaker GitHub repos, the bot is does two things:
-
Handle events as
they arrive in the repositories you've configured.The bot applies each listed event handler in
event.handlersto every event
it sees since the last time it ran. It keeps track of this by writing the
timestamp of the newest event it processed into either local storage, or GCS
(depends on thestorageconfiguration). -
Apply policies to issues, pull
requests, and maybe more in the
future.The bot pulls every issue/pull request from each repository, and applies
eachpolicy.policiesto it. This is quite a bit more expensive (in API
calls) and ideally shouldn't be done with every run.
The reason for handling these two things separately is that old issues/pull
requests don't generate events, but need attention.
Writing a new event handler
To create a new event handler, create a file: events/my_event_handler.py:
from .handler import Handler
# !IMPORTANT! The class name must match the "snake_case" of the filename. This
# is how the handler is automatically configured & registered when
# entered in ~/.spinbot/config
class MyEventHandler(Handler):
def __init__(self):
super().__init__()
# Calling that init function gives you the following:
# * self.config (comes from the per-event-handler config in
# events.handler.*.config)
# * self.logging (a logger just for this class)
def handles(self, event):
# return True i.f.f. the input event can be handled by this handler
def handle(self, gh, event):
# gh is client under ./gh/client.py -- it's meant to wrap API calls
# event is the event to process
# !IMPORTANT! Call your constructur here
MyEventHandler()And configure it using:
...
event:
handlers:
- name: my_event_handler
config:
custom: 'value'
...Writing a new policy
To create a new policy, create a file: policy/my_policy.py:
from .policy import Policy
# !IMPORTANT! The class name must match the "snake_case" of the filename. This
# is how the policy is automatically configured & registered when
# entered in ~/.spinbot/config
class MyPolicy(Policy):
def __init__(self):
super().__init__()
# Calling that init function gives you the following:
# * self.config (comes from the per-policy config in
# policy.policies.*.config)
# * self.logging (a logger just for this class)
def applies(self, object):
# return True i.f.f. the input object applies to this policy
def apply(self, gh, object):
# gh is client under ./gh/client.py -- it's meant to wrap API calls
# object is the resource to process
# !IMPORTANT! Call your constructur here
MyPolicy()And configure it using:
...
policy:
policies:
- name: my_policy
config:
custom: 'value'
...