Source code for util
import json
import sys
import yaml
import os
import requests
import ipaddress
from github import Github
from gnome.gh import Repo
import gnome.policies
# FIXME: DEBUG should be comming from the environment!!!
DEBUG=True
GITHUB_SPECIAL_HEADERS = ('X-Hub-Signature',
'X-GitHub-Delivery',
'X-GitHub-Event')
# FIXME: elaborate on list of forbidden policy names
# "Policy" is the abstract base class defined in gnome.policies.__init__.py
# We should probably prohibit all the __builtins__ and other python
# reserved words, becuase havok.
#
# we must be extra careful because of eval
FORBIDDEN_POLICY_NAMES = ("Policy",)
# FIXME: use a cache (all over the place)
[docs]class InvalidPayloadJSONError(Exception): pass
[docs]class Config(object):
"""
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.
"""
def __init__(self, callback):
self.callback = callback
self.payload = callback.payload()
self._yaml = None
[docs] def get_yaml(self):
if self._yaml:
return self._yaml
repo_fullname = self.payload['repository']['full_name']
self._yaml = Repo(repo_fullname).get_config()
return self._yaml
[docs] def get_activities(self):
"""
This is the magic method. It processes the config (from .gnome.yml)
and instantiates the policies, which are presumably dispatched.
"""
activities = []
bad_news = []
if not self.yaml_is_valid():
# FIXME: figure out what to do without valid config
raise Exception("debug this please")
# FIXME: maybe nicer if get_yaml returned parsed yaml
# rather than string (and be named differently)
parsed_yml = self.get_yaml()
for policy_name in parsed_yml['policies']:
if policy_name in FORBIDDEN_POLICY_NAMES:
bad_news.append((policy_name, "forbidden"))
elif policy_name not in gnome.policies.MANIFEST.keys():
bad_news.append((policy_name, "not found"))
else:
policy_class = gnome.policies.MANIFEST.get(policy_name, None)
activities.append(policy_class(self, self.callback))
if len(bad_news) > 0:
# FIXME: do something smarter with bad news
# maybe a BadNewsProcessor, that makes information
# available to the repo owner/subscriber.
msg = "BAD NEWS: {}".format(bad_news)
print(msg, file=sys.stdout)
return activities
[docs] def yaml_is_valid(self):
parsed_yml = self.get_yaml()
if 'policies' not in parsed_yml:
if DEBUG:
msg = "policies not in parsed yaml"
print(msg, file=sys.stdout)
print(parsed_yml, file=sys.stdout)
return False
return True
[docs]class CallbackEvent(object):
"""
CallbackEvent is an abstraction over the raw flask request object.
It provides convenience methods for validation and payload access.
"""
def __init__(self, request):
self.request = request
self._payload=None
[docs] def payload(self):
# try the fast way
if self._payload:
return self._payload
# then try the slow way
data = self.request.data
try:
payload=json.loads(data)
self._payload = payload
return payload
except json.JSONDecodeError:
raise InvalidPayloadJSONError()
[docs] def is_valid(self):
headers = self.headers()
for h in GITHUB_SPECIAL_HEADERS:
if h not in headers:
return False
# FIXME: unit-test coverage for callback validation required
try:
p = self.payload()
if not 'repository' in p:
if DEBUG:
msg = "payload does not contain a repository"
print(msg, file=sys.stdout)
print(json.dumps(p, indent=4), file=sys.stdout)
return False
repo_data = p['repository']
if 'full_name' not in repo_data:
if DEBUG:
msg = "repo_data does not have a full_name"
print(msg, file=sys.stdout)
print(json.dumps(repo_data, indent=4), file=sys.stdout)
return False
return True
except InvalidPayloadJSONError:
return False