##// END OF EJS Templates
integrations: webhook, allow to set a custom header. Fixes #5384
marcink -
r2086:d7985951 default
parent child
Show More
@@ -29,6 +29,7 import colander
29 from celery.task import task
29 from celery.task import task
30 from requests.packages.urllib3.util.retry import Retry
30 from requests.packages.urllib3.util.retry import Retry
31
31
32 import rhodecode
32 from rhodecode import events
33 from rhodecode import events
33 from rhodecode.translation import _
34 from rhodecode.translation import _
34 from rhodecode.integrations.types.base import IntegrationTypeBase
35 from rhodecode.integrations.types.base import IntegrationTypeBase
@@ -61,9 +62,10 URL_VARS = ', '.join('${' + x + '}' for
61
62
62
63
63 class WebhookHandler(object):
64 class WebhookHandler(object):
64 def __init__(self, template_url, secret_token):
65 def __init__(self, template_url, secret_token, headers):
65 self.template_url = template_url
66 self.template_url = template_url
66 self.secret_token = secret_token
67 self.secret_token = secret_token
68 self.headers = headers
67
69
68 def get_base_parsed_template(self, data):
70 def get_base_parsed_template(self, data):
69 """
71 """
@@ -117,18 +119,18 class WebhookHandler(object):
117 # register per-commit call
119 # register per-commit call
118 log.debug(
120 log.debug(
119 'register webhook call(%s) to url %s', event, commit_url)
121 'register webhook call(%s) to url %s', event, commit_url)
120 url_cals.append((commit_url, self.secret_token, data))
122 url_cals.append((commit_url, self.secret_token, self.headers, data))
121
123
122 else:
124 else:
123 # register per-branch call
125 # register per-branch call
124 log.debug(
126 log.debug(
125 'register webhook call(%s) to url %s', event, branch_url)
127 'register webhook call(%s) to url %s', event, branch_url)
126 url_cals.append((branch_url, self.secret_token, data))
128 url_cals.append((branch_url, self.secret_token, self.headers, data))
127
129
128 else:
130 else:
129 log.debug(
131 log.debug(
130 'register webhook call(%s) to url %s', event, url)
132 'register webhook call(%s) to url %s', event, url)
131 url_cals.append((url, self.secret_token, data))
133 url_cals.append((url, self.secret_token, self.headers, data))
132
134
133 return url_cals
135 return url_cals
134
136
@@ -136,7 +138,7 class WebhookHandler(object):
136 url = self.get_base_parsed_template(data)
138 url = self.get_base_parsed_template(data)
137 log.debug(
139 log.debug(
138 'register webhook call(%s) to url %s', event, url)
140 'register webhook call(%s) to url %s', event, url)
139 return [(url, self.secret_token, data)]
141 return [(url, self.secret_token, self.headers, data)]
140
142
141 def pull_request_event_handler(self, event, data):
143 def pull_request_event_handler(self, event, data):
142 url = self.get_base_parsed_template(data)
144 url = self.get_base_parsed_template(data)
@@ -145,7 +147,7 class WebhookHandler(object):
145 url = string.Template(url).safe_substitute(
147 url = string.Template(url).safe_substitute(
146 pull_request_id=data['pullrequest']['pull_request_id'],
148 pull_request_id=data['pullrequest']['pull_request_id'],
147 pull_request_url=data['pullrequest']['url'])
149 pull_request_url=data['pullrequest']['url'])
148 return [(url, self.secret_token, data)]
150 return [(url, self.secret_token, self.headers, data)]
149
151
150 def __call__(self, event, data):
152 def __call__(self, event, data):
151 if isinstance(event, events.RepoPushEvent):
153 if isinstance(event, events.RepoPushEvent):
@@ -178,11 +180,32 class WebhookSettingsSchema(colander.Sch
178 secret_token = colander.SchemaNode(
180 secret_token = colander.SchemaNode(
179 colander.String(),
181 colander.String(),
180 title=_('Secret Token'),
182 title=_('Secret Token'),
181 description=_('String used to validate received payloads.'),
183 description=_('String used to validate received payloads. It will be '
184 'sent together with event data in JSON'),
182 default='',
185 default='',
183 missing='',
186 missing='',
184 widget=deform.widget.TextInputWidget(
187 widget=deform.widget.TextInputWidget(
185 placeholder='secret_token'
188 placeholder='e.g. secret_token'
189 ),
190 )
191 custom_header_key = colander.SchemaNode(
192 colander.String(),
193 title=_('Custom Header Key'),
194 description=_('Custom Header name to be set when calling endpoint.'),
195 default='',
196 missing='',
197 widget=deform.widget.TextInputWidget(
198 placeholder='e.g.Authorization'
199 ),
200 )
201 custom_header_val = colander.SchemaNode(
202 colander.String(),
203 title=_('Custom Header Value'),
204 description=_('Custom Header value to be set when calling endpoint.'),
205 default='',
206 missing='',
207 widget=deform.widget.TextInputWidget(
208 placeholder='e.g. RcLogin auth=xxxx'
186 ),
209 ),
187 )
210 )
188 method_type = colander.SchemaNode(
211 method_type = colander.SchemaNode(
@@ -245,7 +268,15 class WebhookIntegrationType(Integration
245 data = event.as_dict()
268 data = event.as_dict()
246 template_url = self.settings['url']
269 template_url = self.settings['url']
247
270
248 handler = WebhookHandler(template_url, self.settings['secret_token'])
271 headers = {}
272 head_key = self.settings['custom_header_key']
273 head_val = self.settings['custom_header_val']
274 if head_key and head_val:
275 headers = {head_key: head_val}
276
277 handler = WebhookHandler(
278 template_url, self.settings['secret_token'], headers)
279
249 url_calls = handler(event, data)
280 url_calls = handler(event, data)
250 log.debug('webhook: calling following urls: %s',
281 log.debug('webhook: calling following urls: %s',
251 [x[0] for x in url_calls])
282 [x[0] for x in url_calls])
@@ -259,7 +290,12 def post_to_webhook(url_calls, settings)
259 total=max_retries,
290 total=max_retries,
260 backoff_factor=0.15,
291 backoff_factor=0.15,
261 status_forcelist=[500, 502, 503, 504])
292 status_forcelist=[500, 502, 503, 504])
262 for url, token, data in url_calls:
293 call_headers = {
294 'User-Agent': 'RhodeCode-webhook-caller/{}'.format(
295 rhodecode.__version__)
296 } # updated below with custom ones, allows override
297
298 for url, token, headers, data in url_calls:
263 req_session = requests.Session()
299 req_session = requests.Session()
264 req_session.mount( # retry max N times
300 req_session.mount( # retry max N times
265 'http://', requests.adapters.HTTPAdapter(max_retries=retries))
301 'http://', requests.adapters.HTTPAdapter(max_retries=retries))
@@ -267,10 +303,14 def post_to_webhook(url_calls, settings)
267 method = settings.get('method_type') or 'post'
303 method = settings.get('method_type') or 'post'
268 call_method = getattr(req_session, method)
304 call_method = getattr(req_session, method)
269
305
306 headers = headers or {}
307 call_headers.update(headers)
308
270 log.debug('calling WEBHOOK with method: %s', call_method)
309 log.debug('calling WEBHOOK with method: %s', call_method)
271 resp = call_method(url, json={
310 resp = call_method(url, json={
272 'token': token,
311 'token': token,
273 'event': data
312 'event': data
274 })
313 }, headers=call_headers)
275 log.debug('Got WEBHOOK response: %s', resp)
314 log.debug('Got WEBHOOK response: %s', resp)
315
276 resp.raise_for_status() # raise exception on a failed request
316 resp.raise_for_status() # raise exception on a failed request
@@ -44,7 +44,8 def base_data():
44
44
45 def test_webhook_parse_url_invalid_event():
45 def test_webhook_parse_url_invalid_event():
46 template_url = 'http://server.com/${repo_name}/build'
46 template_url = 'http://server.com/${repo_name}/build'
47 handler = WebhookHandler(template_url, 'secret_token')
47 handler = WebhookHandler(
48 template_url, 'secret_token', {'exmaple-header':'header-values'})
48 with pytest.raises(ValueError) as err:
49 with pytest.raises(ValueError) as err:
49 handler(events.RepoDeleteEvent(''), {})
50 handler(events.RepoDeleteEvent(''), {})
50 assert str(err.value).startswith('event type not supported')
51 assert str(err.value).startswith('event type not supported')
@@ -57,24 +58,32 def test_webhook_parse_url_invalid_event
57 ('http://server.com/${branch}/build', ['http://server.com/${branch}/build']),
58 ('http://server.com/${branch}/build', ['http://server.com/${branch}/build']),
58 ])
59 ])
59 def test_webook_parse_url_for_create_event(base_data, template, expected_urls):
60 def test_webook_parse_url_for_create_event(base_data, template, expected_urls):
60 handler = WebhookHandler(template, 'secret_token')
61 headers = {'exmaple-header': 'header-values'}
62 handler = WebhookHandler(
63 template, 'secret_token', headers)
61 urls = handler(events.RepoCreateEvent(''), base_data)
64 urls = handler(events.RepoCreateEvent(''), base_data)
62 assert urls == [(url, 'secret_token', base_data) for url in expected_urls]
65 assert urls == [
66 (url, 'secret_token', headers, base_data) for url in expected_urls]
63
67
64
68
65 @pytest.mark.parametrize('template,expected_urls', [
69 @pytest.mark.parametrize('template,expected_urls', [
66 ('http://server.com/${repo_name}/${pull_request_id}', ['http://server.com/foo/999']),
70 ('http://server.com/${repo_name}/${pull_request_id}', ['http://server.com/foo/999']),
67 ('http://server.com/${repo_name}/${pull_request_url}', ['http://server.com/foo/http://pr-url.com']),
71 ('http://server.com/${repo_name}/${pull_request_url}', ['http://server.com/foo/http://pr-url.com']),
68 ])
72 ])
69 def test_webook_parse_url_for_pull_request_event(base_data, template, expected_urls):
73 def test_webook_parse_url_for_pull_request_event(
74 base_data, template, expected_urls):
75
70 base_data['pullrequest'] = {
76 base_data['pullrequest'] = {
71 'pull_request_id': 999,
77 'pull_request_id': 999,
72 'url': 'http://pr-url.com',
78 'url': 'http://pr-url.com',
73 }
79 }
74 handler = WebhookHandler(template, 'secret_token')
80 headers = {'exmaple-header': 'header-values'}
81 handler = WebhookHandler(
82 template, 'secret_token', headers)
75 urls = handler(events.PullRequestCreateEvent(
83 urls = handler(events.PullRequestCreateEvent(
76 AttributeDict({'target_repo': 'foo'})), base_data)
84 AttributeDict({'target_repo': 'foo'})), base_data)
77 assert urls == [(url, 'secret_token', base_data) for url in expected_urls]
85 assert urls == [
86 (url, 'secret_token', headers, base_data) for url in expected_urls]
78
87
79
88
80 @pytest.mark.parametrize('template,expected_urls', [
89 @pytest.mark.parametrize('template,expected_urls', [
@@ -85,7 +94,8 def test_webook_parse_url_for_pull_reque
85 'http://server.com/dev/dev-xxx',
94 'http://server.com/dev/dev-xxx',
86 'http://server.com/dev/dev-yyy']),
95 'http://server.com/dev/dev-yyy']),
87 ])
96 ])
88 def test_webook_parse_url_for_push_event(pylonsapp, repo_push_event, base_data, template, expected_urls):
97 def test_webook_parse_url_for_push_event(
98 pylonsapp, repo_push_event, base_data, template, expected_urls):
89 base_data['push'] = {
99 base_data['push'] = {
90 'branches': [{'name': 'stable'}, {'name': 'dev'}],
100 'branches': [{'name': 'stable'}, {'name': 'dev'}],
91 'commits': [{'branch': 'stable', 'raw_id': 'stable-xxx'},
101 'commits': [{'branch': 'stable', 'raw_id': 'stable-xxx'},
@@ -93,6 +103,9 def test_webook_parse_url_for_push_event
93 {'branch': 'dev', 'raw_id': 'dev-xxx'},
103 {'branch': 'dev', 'raw_id': 'dev-xxx'},
94 {'branch': 'dev', 'raw_id': 'dev-yyy'}]
104 {'branch': 'dev', 'raw_id': 'dev-yyy'}]
95 }
105 }
96 handler = WebhookHandler(template, 'secret_token')
106 headers = {'exmaple-header': 'header-values'}
107 handler = WebhookHandler(
108 template, 'secret_token', headers)
97 urls = handler(repo_push_event, base_data)
109 urls = handler(repo_push_event, base_data)
98 assert urls == [(url, 'secret_token', base_data) for url in expected_urls]
110 assert urls == [
111 (url, 'secret_token', headers, base_data) for url in expected_urls]
General Comments 0
You need to be logged in to leave comments. Login now