Permalink
Show file tree
Hide file tree
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Merge pull request #2179 from ryansydnor/hipchat
Add Hipchat reporter
- Loading branch information
Showing
7 changed files
with
399 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
from twisted.internet import defer | ||
from twisted.python import log | ||
|
||
from buildbot import config | ||
from buildbot.process.results import statusToString | ||
from buildbot.reporters import utils | ||
from buildbot.reporters.http import HttpStatusPushBase | ||
|
||
|
||
class HipChatStatusPush(HttpStatusPushBase): | ||
name = "HipChatStatusPush" | ||
|
||
def checkConfig(self, auth_token, endpoint="https://api.hipchat.com", | ||
builder_room_map=None, builder_user_map=None, | ||
event_messages=None, **kwargs): | ||
if not isinstance(auth_token, basestring): | ||
config.error('auth_token must be a string') | ||
if not isinstance(endpoint, basestring): | ||
config.error('endpoint must be a string') | ||
if builder_room_map and not isinstance(builder_room_map, dict): | ||
config.error('builder_room_map must be a dict') | ||
if builder_user_map and not isinstance(builder_user_map, dict): | ||
config.error('builder_user_map must be a dict') | ||
|
||
@defer.inlineCallbacks | ||
def reconfigService(self, auth_token, endpoint="https://api.hipchat.com", | ||
builder_room_map=None, builder_user_map=None, | ||
event_messages=None, **kwargs): | ||
yield HttpStatusPushBase.reconfigService(self, **kwargs) | ||
self.auth_token = auth_token | ||
self.endpoint = endpoint | ||
self.builder_room_map = builder_room_map | ||
self.builder_user_map = builder_user_map | ||
self.user_notify = '%sv2/user/%s/message?auth_token=%s' | ||
self.room_notify = '%sv2/room/%s/notification?auth_token=%s' | ||
|
||
@defer.inlineCallbacks | ||
def buildStarted(self, key, build): | ||
yield self.send(build, key[2]) | ||
|
||
@defer.inlineCallbacks | ||
def buildFinished(self, key, build): | ||
yield self.send(build, key[2]) | ||
|
||
@defer.inlineCallbacks | ||
def getBuildDetailsAndSendMessage(self, build, key): | ||
yield utils.getDetailsForBuild(self.master, build, **self.neededDetails) | ||
postData = yield self.getRecipientList(build, key) | ||
postData['message'] = yield self.getMessage(build, key) | ||
extra_params = yield self.getExtraParams(build, key) | ||
postData.update(extra_params) | ||
defer.returnValue(postData) | ||
|
||
def getRecipientList(self, build, event_name): | ||
result = {} | ||
builder_name = build['builder']['name'] | ||
if self.builder_user_map and builder_name in self.builder_user_map: | ||
result['id_or_email'] = self.builder_user_map[builder_name] | ||
if self.builder_room_map and builder_name in self.builder_room_map: | ||
result['room_id_or_name'] = self.builder_room_map[builder_name] | ||
return result | ||
|
||
def getMessage(self, build, event_name): | ||
event_messages = { | ||
'new': 'Buildbot started build %s here: %s' % (build['builder']['name'], build['url']), | ||
'finished': 'Buildbot finished build %s with result %s here: %s' | ||
% (build['builder']['name'], statusToString(build['results']), build['url']) | ||
} | ||
return event_messages.get(event_name, '') | ||
|
||
# use this as an extension point to inject extra parameters into your postData | ||
def getExtraParams(self, build, event_name): | ||
return {} | ||
|
||
@defer.inlineCallbacks | ||
def send(self, build, key): | ||
postData = yield self.getBuildDetailsAndSendMessage(build, key) | ||
if not postData or 'message' not in postData or not postData['message']: | ||
return | ||
|
||
if not self.endpoint.endswith('/'): | ||
self.endpoint += '/' | ||
|
||
urls = [] | ||
if 'id_or_email' in postData: | ||
urls.append(self.user_notify % (self.endpoint, postData.pop('id_or_email'), self.auth_token)) | ||
if 'room_id_or_name' in postData: | ||
urls.append(self.room_notify % (self.endpoint, postData.pop('room_id_or_name'), self.auth_token)) | ||
|
||
if urls: | ||
for url in urls: | ||
response = yield self.session.post(url, postData) | ||
if response.status_code != 200: | ||
log.msg("%s: unable to upload status: %s" % | ||
(response.status_code, response.content)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
# 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 Buildbot Team Members | ||
from mock import Mock | ||
from mock import call | ||
from twisted.internet import defer | ||
from twisted.trial import unittest | ||
from buildbot import config | ||
from buildbot.process.results import SUCCESS | ||
from buildbot.reporters.hipchat import HipChatStatusPush | ||
from buildbot.test.fake import fakemaster | ||
from buildbot.test.util.reporter import ReporterTestMixin | ||
|
||
|
||
class TestHipchatStatusPush(unittest.TestCase, ReporterTestMixin): | ||
|
||
def setUp(self): | ||
# ignore config error if txrequests is not installed | ||
config._errors = Mock() | ||
self.master = fakemaster.make_master(testcase=self, wantData=True, wantDb=True, wantMq=True) | ||
|
||
@defer.inlineCallbacks | ||
def tearDown(self): | ||
yield self.sp.stopService() | ||
self.assertEqual(self.sp.session.close.call_count, 1) | ||
config._errors = None | ||
|
||
@defer.inlineCallbacks | ||
def createReporter(self, **kwargs): | ||
kwargs['auth_token'] = kwargs.get('auth_token', 'abc') | ||
self.sp = HipChatStatusPush(**kwargs) | ||
self.sp.sessionFactory = Mock(return_value=Mock()) | ||
yield self.sp.setServiceParent(self.master) | ||
yield self.sp.startService() | ||
|
||
@defer.inlineCallbacks | ||
def setupBuildResults(self): | ||
self.insertTestData([SUCCESS], SUCCESS) | ||
build = yield self.master.data.get(("builds", 20)) | ||
defer.returnValue(build) | ||
|
||
@defer.inlineCallbacks | ||
def test_authtokenTypeCheck(self): | ||
yield self.createReporter(auth_token=2) | ||
config._errors.addError.assert_any_call('auth_token must be a string') | ||
|
||
@defer.inlineCallbacks | ||
def test_endpointTypeCheck(self): | ||
yield self.createReporter(endpoint=2) | ||
config._errors.addError.assert_any_call('endpoint must be a string') | ||
|
||
@defer.inlineCallbacks | ||
def test_builderRoomMapTypeCheck(self): | ||
yield self.createReporter(builder_room_map=2) | ||
config._errors.addError.assert_any_call('builder_room_map must be a dict') | ||
|
||
@defer.inlineCallbacks | ||
def test_builderUserMapTypeCheck(self): | ||
yield self.createReporter(builder_user_map=2) | ||
config._errors.addError.assert_any_call('builder_user_map must be a dict') | ||
|
||
@defer.inlineCallbacks | ||
def test_build_started(self): | ||
yield self.createReporter(builder_user_map={'Builder0': '123'}) | ||
build = yield self.setupBuildResults() | ||
self.sp.buildStarted(('build', 20, 'new'), build) | ||
expected = [call('https://api.hipchat.com/v2/user/123/message?auth_token=abc', | ||
{'message': 'Buildbot started build Builder0 here: http://localhost:8080/#builders/79/builds/0'})] | ||
self.assertEqual(self.sp.session.post.mock_calls, expected) | ||
|
||
@defer.inlineCallbacks | ||
def test_build_finished(self): | ||
yield self.createReporter(builder_room_map={'Builder0': '123'}) | ||
build = yield self.setupBuildResults() | ||
self.sp.buildFinished(('build', 20, 'finished'), build) | ||
expected = [call('https://api.hipchat.com/v2/room/123/notification?auth_token=abc', | ||
{'message': 'Buildbot finished build Builder0 with result success ' | ||
'here: http://localhost:8080/#builders/79/builds/0'})] | ||
self.assertEqual(self.sp.session.post.mock_calls, expected) | ||
|
||
@defer.inlineCallbacks | ||
def test_inject_extra_params(self): | ||
yield self.createReporter(builder_room_map={'Builder0': '123'}) | ||
self.sp.getExtraParams = Mock() | ||
self.sp.getExtraParams.return_value = {'format': 'html'} | ||
build = yield self.setupBuildResults() | ||
self.sp.buildFinished(('build', 20, 'finished'), build) | ||
expected = [call('https://api.hipchat.com/v2/room/123/notification?auth_token=abc', | ||
{'message': 'Buildbot finished build Builder0 with result success ' | ||
'here: http://localhost:8080/#builders/79/builds/0', | ||
'format': 'html'})] | ||
self.assertEqual(self.sp.session.post.mock_calls, expected) | ||
|
||
@defer.inlineCallbacks | ||
def test_no_message_sent_empty_message(self): | ||
yield self.createReporter() | ||
build = yield self.setupBuildResults() | ||
self.sp.send(build, 'unknown') | ||
assert not self.sp.session.post.called | ||
|
||
@defer.inlineCallbacks | ||
def test_no_message_sent_without_id(self): | ||
yield self.createReporter() | ||
build = yield self.setupBuildResults() | ||
self.sp.send(build, 'new') | ||
assert not self.sp.session.post.called | ||
|
||
@defer.inlineCallbacks | ||
def test_private_message_sent_with_user_id(self): | ||
token = 'tok' | ||
endpoint = 'example.com' | ||
yield self.createReporter(auth_token=token, endpoint=endpoint) | ||
self.sp.getBuildDetailsAndSendMessage = Mock() | ||
message = {'message': 'hi'} | ||
postData = dict(message) | ||
postData.update({'id_or_email': '123'}) | ||
self.sp.getBuildDetailsAndSendMessage.return_value = postData | ||
self.sp.send({}, 'test') | ||
expected = [call('%s/v2/user/123/message?auth_token=%s' % (endpoint, token), message)] | ||
self.assertEqual(self.sp.session.post.mock_calls, expected) | ||
|
||
@defer.inlineCallbacks | ||
def test_room_message_sent_with_room_id(self): | ||
token = 'tok' | ||
endpoint = 'example.com' | ||
yield self.createReporter(auth_token=token, endpoint=endpoint) | ||
self.sp.getBuildDetailsAndSendMessage = Mock() | ||
message = {'message': 'hi'} | ||
postData = dict(message) | ||
postData.update({'room_id_or_name': '123'}) | ||
self.sp.getBuildDetailsAndSendMessage.return_value = postData | ||
self.sp.send({}, 'test') | ||
expected = [call('%s/v2/room/123/notification?auth_token=%s' % (endpoint, token), message)] | ||
self.assertEqual(self.sp.session.post.mock_calls, expected) | ||
|
||
@defer.inlineCallbacks | ||
def test_private_and_room_message_sent_with_both_ids(self): | ||
token = 'tok' | ||
endpoint = 'example.com' | ||
yield self.createReporter(auth_token=token, endpoint=endpoint) | ||
self.sp.getBuildDetailsAndSendMessage = Mock() | ||
message = {'message': 'hi'} | ||
postData = dict(message) | ||
postData.update({'room_id_or_name': '123', 'id_or_email': '456'}) | ||
self.sp.getBuildDetailsAndSendMessage.return_value = postData | ||
self.sp.send({}, 'test') | ||
expected = [call('%s/v2/user/456/message?auth_token=%s' % (endpoint, token), message), | ||
call('%s/v2/room/123/notification?auth_token=%s' % (endpoint, token), message)] | ||
self.assertEqual(self.sp.session.post.mock_calls, expected) | ||
|
||
@defer.inlineCallbacks | ||
def test_postData_values_passed_through(self): | ||
token = 'tok' | ||
endpoint = 'example.com' | ||
yield self.createReporter(auth_token=token, endpoint=endpoint) | ||
self.sp.getBuildDetailsAndSendMessage = Mock() | ||
message = {'message': 'hi', 'notify': True, 'message_format': 'html'} | ||
postData = dict(message) | ||
postData.update({'id_or_email': '123'}) | ||
self.sp.getBuildDetailsAndSendMessage.return_value = postData | ||
self.sp.send({}, 'test') | ||
expected = [call('%s/v2/user/123/message?auth_token=%s' % (endpoint, token), message)] | ||
self.assertEqual(self.sp.session.post.mock_calls, expected) |
Oops, something went wrong.