Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a web hook handler for Google Code push notifications #291

Merged
merged 2 commits into from Dec 19, 2011
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
97 changes: 97 additions & 0 deletions master/buildbot/status/web/hooks/googlecode.py
@@ -0,0 +1,97 @@
# This file is part of Buildbot. Buildbot is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright 2011, Louis Opter <kalessin@kalessin.fr>

# Quite inspired from the github hook.

import hmac
import logging
import sys
import traceback

from twisted.python import log

from buildbot.util import json

class GoogleCodeAuthFailed(Exception):
pass

class Payload(object):
def __init__(self, headers, body, branch):
self._auth_code = headers['Google-Code-Project-Hosting-Hook-Hmac']
self._body = body # we need to save it if we want to authenticate it
self._branch = branch

payload = json.loads(body)
self.project = payload['project_name']
self.repository = payload['repository_path']
self.revisions = payload['revisions']
self.revision_count = payload['revision_count']

def authenticate(self, secret_key):
m = hmac.new(secret_key)
m.update(self._body)
digest = m.hexdigest()
return digest == self._auth_code

def changes(self):
changes = []

for r in self.revisions:
files = set()
files.update(r['added'], r['modified'], r['removed'])
changes.append(dict(
who=r['author'],
files=list(files),
comments=r['message'],
links=[r['url']],
revision=r['revision'],
when=r['timestamp'],
# Let's hope Google add the branch one day:
branch=r.get('branch', self._branch),
revlink=r['url'],
repository=self.repository,
project=self.project
))

return changes

def getChanges(request, options=None):
try:
headers = request.received_headers
body = request.content.getvalue()
#logging.error('headers = {0}, body = {1}'.format(headers, body))

# Instantiate a Payload object: this will parse the body, get the
# authentication code from the headers and remember the branch picked up
# by the user (Google Code doesn't send on which branch the changes were
# made)
payload = Payload(headers, body, options.get('branch', 'default'))

if 'secret_key' in options:
if not payload.authenticate(options['secret_key']):
raise GoogleCodeAuthFailed()
else:
log.msg('Missing secret_key in the Google Code WebHook options: cannot authenticate the request!')

log.msg('Received {0} changes from Google Code'.format(payload.revision_count))
changes = payload.changes()
except:
logging.error("Can't parse the Google Code WebHook:")
for msg in traceback.format_exception(*sys.exc_info()):
logging.error(msg.strip())
# return something valid even if everything goes wrong:
changes = []

return changes, 'Google Code'
@@ -0,0 +1,90 @@
# This file is part of Buildbot. Buildbot is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright 2011 Louis Opter <kalessin@kalessin.fr>
#
# Written from the github change hook unit test

import StringIO

import buildbot.status.web.change_hook as change_hook
from buildbot.test.fake.web import MockRequest

from twisted.trial import unittest
from twisted.internet import defer

# Sample Google Code commit payload extracted from a Google Code test project
# {
# "repository_path": "https://code.google.com/p/webhook-test/",
# "project_name": "webhook-test",
# "revision_count": 1,
# "revisions": [
# {
# "added": [],
# "parents": ["6574485e26a09a0e743e0745374056891d6a836a"],
# "author": "Louis Opter \\u003Clouis@lse.epitech.net\\u003E",
# "url": "http://webhook-test.googlecode.com/hg-history/68e5df283a8e751cdbf95516b20357b2c46f93d4/",
# "timestamp": 1324082130,
# "message": "Print a message",
# "path_count": 1,
# "removed": [],
# "modified": ["/CMakeLists.txt"],
# "revision": "68e5df283a8e751cdbf95516b20357b2c46f93d4"
# }
# ]
# }
googleCodeJsonBody = '{"repository_path":"https://code.google.com/p/webhook-test/","project_name":"webhook-test","revisions":[{"added":[],"parents":["6574485e26a09a0e743e0745374056891d6a836a"],"author":"Louis Opter \u003Clouis@lse.epitech.net\u003E","url":"http://webhook-test.googlecode.com/hg-history/68e5df283a8e751cdbf95516b20357b2c46f93d4/","timestamp":1324082130,"message":"Print a message","path_count":1,"removed":[],"modified":["/CMakeLists.txt"],"revision":"68e5df283a8e751cdbf95516b20357b2c46f93d4"}],"revision_count":1}'

class TestChangeHookConfiguredWithGoogleCodeChange(unittest.TestCase):
def setUp(self):
self.request = MockRequest()
# Google Code simply transmit the payload as an UTF-8 JSON body
self.request.content = StringIO.StringIO(googleCodeJsonBody)
self.request.received_headers = {
'Google-Code-Project-Hosting-Hook-Hmac': '85910bf93ba5c266402d9328b0c7a856',
'Content-Length': '509',
'Accept-Encoding': 'gzip',
'User-Agent': 'Google Code Project Hosting (+http://code.google.com/p/support/wiki/PostCommitWebHooks)',
'Host': 'buildbot6-lopter.dotcloud.com:19457',
'Content-Type': 'application/json; charset=UTF-8'
}

self.changeHook = change_hook.ChangeHookResource(dialects={
'googlecode': {
'secret_key': 'FSP3p-Ghdn4T0oqX',
'branch': 'test'
}
})

# Test 'base' hook with attributes. We should get a json string representing
# a Change object as a dictionary. All values show be set.
def testGoogleCodeWithHgChange(self):
self.request.uri = "/change_hook/googlecode"
d = defer.maybeDeferred(lambda : self.changeHook.render_GET(self.request))
def check_changes(r):
# Only one changeset has been submitted.
self.assertEquals(len(self.request.addedChanges), 1)

# First changeset.
change = self.request.addedChanges[0]
self.assertEquals(change['files'], ['/CMakeLists.txt'])
self.assertEquals(change["repository"], "https://code.google.com/p/webhook-test/")
self.assertEquals(change["when"], 1324082130)
self.assertEquals(change["who"], "Louis Opter <louis@lse.epitech.net>")
self.assertEquals(change["revision"], '68e5df283a8e751cdbf95516b20357b2c46f93d4')
self.assertEquals(change["comments"], "Print a message")
self.assertEquals(change["branch"], "test")
self.assertEquals(change["revlink"], "http://webhook-test.googlecode.com/hg-history/68e5df283a8e751cdbf95516b20357b2c46f93d4/")

d.addCallback(check_changes)
return d
4 changes: 2 additions & 2 deletions master/docs/manual/cfg-changesources.rst
Expand Up @@ -1125,8 +1125,8 @@ Change Hooks (HTTP Notifications)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Buildbot already provides a web frontend, and that frontend can easily be used
to receive HTTP push notifications of commits from services like GitHub. See
:ref:`Change-Hooks` for more information.
to receive HTTP push notifications of commits from services like GitHub or
GoogleCode. See :ref:`Change-Hooks` for more information.

.. bb:chsrc:: GoogleCodeAtomPoller

Expand Down
27 changes: 27 additions & 0 deletions master/docs/manual/cfg-statustargets.rst
Expand Up @@ -682,6 +682,33 @@ Note that there is a standalone HTTP server available for receiving GitHub
notifications, as well: :file:`contrib/github_buildbot.py`. This script may be
useful in cases where you cannot expose the WebStatus for public consumption.

Google Code hook
################

The Google Code hook is quite similar to the GitHub Hook. It has one option
for the "Post-Commit Authentication Key" used to check if the request is
legitimate::

c['status'].append(html.WebStatus(
…,
change_hook_dialects={'googlecode': {'secret_key': 'FSP3p-Ghdn4T0oqX'}}
))

This will add a "Post-Commit URL" for the project in the Google Code
administrative interface, pointing to ``/change_hook/googlecode`` relative to
the root of the web status.

Alternatively, you can use the :ref:`GoogleCodeAtomPoller` :class:`ChangeSource`
that periodically poll the Google Code commit feed for changes.

.. note::

Google Code doesn't send the branch on which the changes were made. So, the
hook always returns ``'default'`` as the branch, you can override it with the
``'branch'`` option::

change_hook_dialects={'googlecode': {'secret_key': 'FSP3p-Ghdn4T0oqX', 'branch': 'master'}}

.. bb:status:: MailNotifier

.. index:: single: email; MailNotifier
Expand Down