Show More
@@ -29,6 +29,7 b' import colander' | |||
|
29 | 29 | from celery.task import task |
|
30 | 30 | from requests.packages.urllib3.util.retry import Retry |
|
31 | 31 | |
|
32 | import rhodecode | |
|
32 | 33 | from rhodecode import events |
|
33 | 34 | from rhodecode.translation import _ |
|
34 | 35 | from rhodecode.integrations.types.base import IntegrationTypeBase |
@@ -61,9 +62,10 b" URL_VARS = ', '.join('${' + x + '}' for " | |||
|
61 | 62 | |
|
62 | 63 | |
|
63 | 64 | class WebhookHandler(object): |
|
64 | def __init__(self, template_url, secret_token): | |
|
65 | def __init__(self, template_url, secret_token, headers): | |
|
65 | 66 | self.template_url = template_url |
|
66 | 67 | self.secret_token = secret_token |
|
68 | self.headers = headers | |
|
67 | 69 | |
|
68 | 70 | def get_base_parsed_template(self, data): |
|
69 | 71 | """ |
@@ -117,18 +119,18 b' class WebhookHandler(object):' | |||
|
117 | 119 | # register per-commit call |
|
118 | 120 | log.debug( |
|
119 | 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 | 124 | else: |
|
123 | 125 | # register per-branch call |
|
124 | 126 | log.debug( |
|
125 | 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 | 130 | else: |
|
129 | 131 | log.debug( |
|
130 | 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 | 135 | return url_cals |
|
134 | 136 | |
@@ -136,7 +138,7 b' class WebhookHandler(object):' | |||
|
136 | 138 | url = self.get_base_parsed_template(data) |
|
137 | 139 | log.debug( |
|
138 | 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 | 143 | def pull_request_event_handler(self, event, data): |
|
142 | 144 | url = self.get_base_parsed_template(data) |
@@ -145,7 +147,7 b' class WebhookHandler(object):' | |||
|
145 | 147 | url = string.Template(url).safe_substitute( |
|
146 | 148 | pull_request_id=data['pullrequest']['pull_request_id'], |
|
147 | 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 | 152 | def __call__(self, event, data): |
|
151 | 153 | if isinstance(event, events.RepoPushEvent): |
@@ -178,11 +180,32 b' class WebhookSettingsSchema(colander.Sch' | |||
|
178 | 180 | secret_token = colander.SchemaNode( |
|
179 | 181 | colander.String(), |
|
180 | 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 | 185 | default='', |
|
183 | 186 | missing='', |
|
184 | 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 | 211 | method_type = colander.SchemaNode( |
@@ -245,7 +268,15 b' class WebhookIntegrationType(Integration' | |||
|
245 | 268 | data = event.as_dict() |
|
246 | 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 | 280 | url_calls = handler(event, data) |
|
250 | 281 | log.debug('webhook: calling following urls: %s', |
|
251 | 282 | [x[0] for x in url_calls]) |
@@ -259,7 +290,12 b' def post_to_webhook(url_calls, settings)' | |||
|
259 | 290 | total=max_retries, |
|
260 | 291 | backoff_factor=0.15, |
|
261 | 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 | 299 | req_session = requests.Session() |
|
264 | 300 | req_session.mount( # retry max N times |
|
265 | 301 | 'http://', requests.adapters.HTTPAdapter(max_retries=retries)) |
@@ -267,10 +303,14 b' def post_to_webhook(url_calls, settings)' | |||
|
267 | 303 | method = settings.get('method_type') or 'post' |
|
268 | 304 | call_method = getattr(req_session, method) |
|
269 | 305 | |
|
306 | headers = headers or {} | |
|
307 | call_headers.update(headers) | |
|
308 | ||
|
270 | 309 | log.debug('calling WEBHOOK with method: %s', call_method) |
|
271 | 310 | resp = call_method(url, json={ |
|
272 | 311 | 'token': token, |
|
273 | 312 | 'event': data |
|
274 | }) | |
|
313 | }, headers=call_headers) | |
|
275 | 314 | log.debug('Got WEBHOOK response: %s', resp) |
|
315 | ||
|
276 | 316 | resp.raise_for_status() # raise exception on a failed request |
@@ -44,7 +44,8 b' def base_data():' | |||
|
44 | 44 | |
|
45 | 45 | def test_webhook_parse_url_invalid_event(): |
|
46 | 46 | template_url = 'http://server.com/${repo_name}/build' |
|
47 |
handler = WebhookHandler( |
|
|
47 | handler = WebhookHandler( | |
|
48 | template_url, 'secret_token', {'exmaple-header':'header-values'}) | |
|
48 | 49 | with pytest.raises(ValueError) as err: |
|
49 | 50 | handler(events.RepoDeleteEvent(''), {}) |
|
50 | 51 | assert str(err.value).startswith('event type not supported') |
@@ -57,24 +58,32 b' def test_webhook_parse_url_invalid_event' | |||
|
57 | 58 | ('http://server.com/${branch}/build', ['http://server.com/${branch}/build']), |
|
58 | 59 | ]) |
|
59 | 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 | 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 | 69 | @pytest.mark.parametrize('template,expected_urls', [ |
|
66 | 70 | ('http://server.com/${repo_name}/${pull_request_id}', ['http://server.com/foo/999']), |
|
67 | 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( |
|
|
73 | def test_webook_parse_url_for_pull_request_event( | |
|
74 | base_data, template, expected_urls): | |
|
75 | ||
|
70 | 76 | base_data['pullrequest'] = { |
|
71 | 77 | 'pull_request_id': 999, |
|
72 | 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 | 83 | urls = handler(events.PullRequestCreateEvent( |
|
76 | 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 | 89 | @pytest.mark.parametrize('template,expected_urls', [ |
@@ -85,7 +94,8 b' def test_webook_parse_url_for_pull_reque' | |||
|
85 | 94 | 'http://server.com/dev/dev-xxx', |
|
86 | 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 | 99 | base_data['push'] = { |
|
90 | 100 | 'branches': [{'name': 'stable'}, {'name': 'dev'}], |
|
91 | 101 | 'commits': [{'branch': 'stable', 'raw_id': 'stable-xxx'}, |
@@ -93,6 +103,9 b' def test_webook_parse_url_for_push_event' | |||
|
93 | 103 | {'branch': 'dev', 'raw_id': 'dev-xxx'}, |
|
94 | 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 | 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