##// END OF EJS Templates
integrations: extract get_auth to common shared code.
marcink -
r2577:39fd91d9 default
parent child Browse files
Show More
@@ -1,113 +1,125 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import colander
22 22 from rhodecode.translation import _
23 23
24 24
25 25 class IntegrationTypeBase(object):
26 26 """ Base class for IntegrationType plugins """
27 27 is_dummy = False
28 28 description = ''
29 29
30 30 @classmethod
31 31 def icon(cls):
32 32 return '''
33 33 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
34 34 <svg
35 35 xmlns:dc="http://purl.org/dc/elements/1.1/"
36 36 xmlns:cc="http://creativecommons.org/ns#"
37 37 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
38 38 xmlns:svg="http://www.w3.org/2000/svg"
39 39 xmlns="http://www.w3.org/2000/svg"
40 40 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
41 41 xmlns:inkscape="http://setwww.inkscape.org/namespaces/inkscape"
42 42 viewBox="0 -256 1792 1792"
43 43 id="svg3025"
44 44 version="1.1"
45 45 inkscape:version="0.48.3.1 r9886"
46 46 width="100%"
47 47 height="100%"
48 48 sodipodi:docname="cog_font_awesome.svg">
49 49 <metadata
50 50 id="metadata3035">
51 51 <rdf:RDF>
52 52 <cc:Work
53 53 rdf:about="">
54 54 <dc:format>image/svg+xml</dc:format>
55 55 <dc:type
56 56 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
57 57 </cc:Work>
58 58 </rdf:RDF>
59 59 </metadata>
60 60 <defs
61 61 id="defs3033" />
62 62 <sodipodi:namedview
63 63 pagecolor="#ffffff"
64 64 bordercolor="#666666"
65 65 borderopacity="1"
66 66 objecttolerance="10"
67 67 gridtolerance="10"
68 68 guidetolerance="10"
69 69 inkscape:pageopacity="0"
70 70 inkscape:pageshadow="2"
71 71 inkscape:window-width="640"
72 72 inkscape:window-height="480"
73 73 id="namedview3031"
74 74 showgrid="false"
75 75 inkscape:zoom="0.13169643"
76 76 inkscape:cx="896"
77 77 inkscape:cy="896"
78 78 inkscape:window-x="0"
79 79 inkscape:window-y="25"
80 80 inkscape:window-maximized="0"
81 81 inkscape:current-layer="svg3025" />
82 82 <g
83 83 transform="matrix(1,0,0,-1,121.49153,1285.4237)"
84 84 id="g3027">
85 85 <path
86 86 d="m 1024,640 q 0,106 -75,181 -75,75 -181,75 -106,0 -181,-75 -75,-75 -75,-181 0,-106 75,-181 75,-75 181,-75 106,0 181,75 75,75 75,181 z m 512,109 V 527 q 0,-12 -8,-23 -8,-11 -20,-13 l -185,-28 q -19,-54 -39,-91 35,-50 107,-138 10,-12 10,-25 0,-13 -9,-23 -27,-37 -99,-108 -72,-71 -94,-71 -12,0 -26,9 l -138,108 q -44,-23 -91,-38 -16,-136 -29,-186 -7,-28 -36,-28 H 657 q -14,0 -24.5,8.5 Q 622,-111 621,-98 L 593,86 q -49,16 -90,37 L 362,16 Q 352,7 337,7 323,7 312,18 186,132 147,186 q -7,10 -7,23 0,12 8,23 15,21 51,66.5 36,45.5 54,70.5 -27,50 -41,99 L 29,495 Q 16,497 8,507.5 0,518 0,531 v 222 q 0,12 8,23 8,11 19,13 l 186,28 q 14,46 39,92 -40,57 -107,138 -10,12 -10,24 0,10 9,23 26,36 98.5,107.5 72.5,71.5 94.5,71.5 13,0 26,-10 l 138,-107 q 44,23 91,38 16,136 29,186 7,28 36,28 h 222 q 14,0 24.5,-8.5 Q 914,1391 915,1378 l 28,-184 q 49,-16 90,-37 l 142,107 q 9,9 24,9 13,0 25,-10 129,-119 165,-170 7,-8 7,-22 0,-12 -8,-23 -15,-21 -51,-66.5 -36,-45.5 -54,-70.5 26,-50 41,-98 l 183,-28 q 13,-2 21,-12.5 8,-10.5 8,-23.5 z"
87 87 id="path3029"
88 88 inkscape:connector-curvature="0"
89 89 style="fill:currentColor" />
90 90 </g>
91 91 </svg>
92 92 '''
93 93
94 94 def __init__(self, settings):
95 95 """
96 96 :param settings: dict of settings to be used for the integration
97 97 """
98 98 self.settings = settings
99 99
100 100 def settings_schema(self):
101 101 """
102 102 A colander schema of settings for the integration type
103 103 """
104 104 return colander.Schema()
105 105
106 106
107 107 class EEIntegration(IntegrationTypeBase):
108 108 description = 'Integration available in RhodeCode EE edition.'
109 109 is_dummy = True
110 110
111 111 def __init__(self, name, key, settings=None):
112 112 self.display_name = name
113 113 self.key = key
114 super(EEIntegration, self).__init__(settings)
115
116
117 # Helpers
118
119 def get_auth(settings):
120 from requests.auth import HTTPBasicAuth
121 username = settings.get('username')
122 password = settings.get('password')
123 if username and password:
124 return HTTPBasicAuth(username, password)
125 return None
@@ -1,398 +1,389 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 from __future__ import unicode_literals
22 22 import string
23 23 from collections import OrderedDict
24 24
25 25 import deform
26 26 import deform.widget
27 27 import logging
28 28 import requests
29 29 import requests.adapters
30 30 import colander
31 31 from requests.packages.urllib3.util.retry import Retry
32 32
33 33 import rhodecode
34 34 from rhodecode import events
35 35 from rhodecode.translation import _
36 from rhodecode.integrations.types.base import IntegrationTypeBase
36 from rhodecode.integrations.types.base import IntegrationTypeBase, get_auth
37 37 from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 # updating this required to update the `common_vars` passed in url calling func
43 43 WEBHOOK_URL_VARS = [
44 44 'repo_name',
45 45 'repo_type',
46 46 'repo_id',
47 47 'repo_url',
48 48 # extra repo fields
49 49 'extra:<extra_key_name>',
50 50
51 51 # special attrs below that we handle, using multi-call
52 52 'branch',
53 53 'commit_id',
54 54
55 55 # pr events vars
56 56 'pull_request_id',
57 57 'pull_request_url',
58 58
59 59 # user who triggers the call
60 60 'username',
61 61 'user_id',
62 62
63 63 ]
64 64 URL_VARS = ', '.join('${' + x + '}' for x in WEBHOOK_URL_VARS)
65 65
66 66
67 def get_auth(settings):
68 from requests.auth import HTTPBasicAuth
69 username = settings.get('username')
70 password = settings.get('password')
71 if username and password:
72 return HTTPBasicAuth(username, password)
73 return None
74
75
76 67 class WebhookHandler(object):
77 68 def __init__(self, template_url, secret_token, headers):
78 69 self.template_url = template_url
79 70 self.secret_token = secret_token
80 71 self.headers = headers
81 72
82 73 def get_base_parsed_template(self, data):
83 74 """
84 75 initially parses the passed in template with some common variables
85 76 available on ALL calls
86 77 """
87 78 # note: make sure to update the `WEBHOOK_URL_VARS` if this changes
88 79 common_vars = {
89 80 'repo_name': data['repo']['repo_name'],
90 81 'repo_type': data['repo']['repo_type'],
91 82 'repo_id': data['repo']['repo_id'],
92 83 'repo_url': data['repo']['url'],
93 84 'username': data['actor']['username'],
94 85 'user_id': data['actor']['user_id']
95 86 }
96 87
97 88 extra_vars = {}
98 89 for extra_key, extra_val in data['repo']['extra_fields'].items():
99 90 extra_vars['extra__{}'.format(extra_key)] = extra_val
100 91 common_vars.update(extra_vars)
101 92
102 93 template_url = self.template_url.replace('${extra:', '${extra__')
103 94 return string.Template(template_url).safe_substitute(**common_vars)
104 95
105 96 def repo_push_event_handler(self, event, data):
106 97 url = self.get_base_parsed_template(data)
107 98 url_cals = []
108 99 branch_data = OrderedDict()
109 100 for obj in data['push']['branches']:
110 101 branch_data[obj['name']] = obj
111 102
112 103 branches_commits = OrderedDict()
113 104 for commit in data['push']['commits']:
114 105 if commit.get('git_ref_change'):
115 106 # special case for GIT that allows creating tags,
116 107 # deleting branches without associated commit
117 108 continue
118 109
119 110 if commit['branch'] not in branches_commits:
120 111 branch_commits = {'branch': branch_data[commit['branch']],
121 112 'commits': []}
122 113 branches_commits[commit['branch']] = branch_commits
123 114
124 115 branch_commits = branches_commits[commit['branch']]
125 116 branch_commits['commits'].append(commit)
126 117
127 118 if '${branch}' in url:
128 119 # call it multiple times, for each branch if used in variables
129 120 for branch, commit_ids in branches_commits.items():
130 121 branch_url = string.Template(url).safe_substitute(branch=branch)
131 122 # call further down for each commit if used
132 123 if '${commit_id}' in branch_url:
133 124 for commit_data in commit_ids['commits']:
134 125 commit_id = commit_data['raw_id']
135 126 commit_url = string.Template(branch_url).safe_substitute(
136 127 commit_id=commit_id)
137 128 # register per-commit call
138 129 log.debug(
139 130 'register webhook call(%s) to url %s', event, commit_url)
140 131 url_cals.append((commit_url, self.secret_token, self.headers, data))
141 132
142 133 else:
143 134 # register per-branch call
144 135 log.debug(
145 136 'register webhook call(%s) to url %s', event, branch_url)
146 137 url_cals.append((branch_url, self.secret_token, self.headers, data))
147 138
148 139 else:
149 140 log.debug(
150 141 'register webhook call(%s) to url %s', event, url)
151 142 url_cals.append((url, self.secret_token, self.headers, data))
152 143
153 144 return url_cals
154 145
155 146 def repo_create_event_handler(self, event, data):
156 147 url = self.get_base_parsed_template(data)
157 148 log.debug(
158 149 'register webhook call(%s) to url %s', event, url)
159 150 return [(url, self.secret_token, self.headers, data)]
160 151
161 152 def pull_request_event_handler(self, event, data):
162 153 url = self.get_base_parsed_template(data)
163 154 log.debug(
164 155 'register webhook call(%s) to url %s', event, url)
165 156 url = string.Template(url).safe_substitute(
166 157 pull_request_id=data['pullrequest']['pull_request_id'],
167 158 pull_request_url=data['pullrequest']['url'])
168 159 return [(url, self.secret_token, self.headers, data)]
169 160
170 161 def __call__(self, event, data):
171 162 if isinstance(event, events.RepoPushEvent):
172 163 return self.repo_push_event_handler(event, data)
173 164 elif isinstance(event, events.RepoCreateEvent):
174 165 return self.repo_create_event_handler(event, data)
175 166 elif isinstance(event, events.PullRequestEvent):
176 167 return self.pull_request_event_handler(event, data)
177 168 else:
178 169 raise ValueError('event type not supported: %s' % events)
179 170
180 171
181 172 class WebhookSettingsSchema(colander.Schema):
182 173 url = colander.SchemaNode(
183 174 colander.String(),
184 175 title=_('Webhook URL'),
185 176 description=
186 177 _('URL to which Webhook should submit data. Following variables '
187 178 'are allowed to be used: {vars}. Some of the variables would '
188 179 'trigger multiple calls, like ${{branch}} or ${{commit_id}}. '
189 180 'Webhook will be called as many times as unique objects in '
190 181 'data in such cases.').format(vars=URL_VARS),
191 182 missing=colander.required,
192 183 required=True,
193 184 validator=colander.url,
194 185 widget=deform.widget.TextInputWidget(
195 186 placeholder='https://www.example.com/webhook'
196 187 ),
197 188 )
198 189 secret_token = colander.SchemaNode(
199 190 colander.String(),
200 191 title=_('Secret Token'),
201 192 description=_('Optional string used to validate received payloads. '
202 193 'It will be sent together with event data in JSON'),
203 194 default='',
204 195 missing='',
205 196 widget=deform.widget.TextInputWidget(
206 197 placeholder='e.g. secret_token'
207 198 ),
208 199 )
209 200 username = colander.SchemaNode(
210 201 colander.String(),
211 202 title=_('Username'),
212 203 description=_('Optional username to authenticate the call.'),
213 204 default='',
214 205 missing='',
215 206 widget=deform.widget.TextInputWidget(
216 207 placeholder='e.g. admin'
217 208 ),
218 209 )
219 210 password = colander.SchemaNode(
220 211 colander.String(),
221 212 title=_('Password'),
222 213 description=_('Optional password to authenticate the call.'),
223 214 default='',
224 215 missing='',
225 216 widget=deform.widget.PasswordWidget(
226 217 placeholder='e.g. secret.',
227 218 redisplay=True,
228 219 ),
229 220 )
230 221 custom_header_key = colander.SchemaNode(
231 222 colander.String(),
232 223 title=_('Custom Header Key'),
233 224 description=_('Custom Header name to be set when calling endpoint.'),
234 225 default='',
235 226 missing='',
236 227 widget=deform.widget.TextInputWidget(
237 228 placeholder='e.g.Authorization'
238 229 ),
239 230 )
240 231 custom_header_val = colander.SchemaNode(
241 232 colander.String(),
242 233 title=_('Custom Header Value'),
243 234 description=_('Custom Header value to be set when calling endpoint.'),
244 235 default='',
245 236 missing='',
246 237 widget=deform.widget.TextInputWidget(
247 238 placeholder='e.g. RcLogin auth=xxxx'
248 239 ),
249 240 )
250 241 method_type = colander.SchemaNode(
251 242 colander.String(),
252 243 title=_('Call Method'),
253 244 description=_('Select if the Webhook call should be made '
254 245 'with POST or GET.'),
255 246 default='post',
256 247 missing='',
257 248 widget=deform.widget.RadioChoiceWidget(
258 249 values=[('get', 'GET'), ('post', 'POST')],
259 250 inline=True
260 251 ),
261 252 )
262 253
263 254
264 255 class WebhookIntegrationType(IntegrationTypeBase):
265 256 key = 'webhook'
266 257 display_name = _('Webhook')
267 258 description = _('Post json events to a Webhook endpoint')
268 259
269 260 @classmethod
270 261 def icon(cls):
271 262 return '''<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg viewBox="0 0 256 239" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid"><g><path d="M119.540432,100.502743 C108.930124,118.338815 98.7646301,135.611455 88.3876025,152.753617 C85.7226696,157.154315 84.4040417,160.738531 86.5332204,166.333309 C92.4107024,181.787152 84.1193605,196.825836 68.5350381,200.908244 C53.8383677,204.759349 39.5192953,195.099955 36.6032893,179.365384 C34.0194114,165.437749 44.8274148,151.78491 60.1824106,149.608284 C61.4694072,149.424428 62.7821041,149.402681 64.944891,149.240571 C72.469175,136.623655 80.1773157,123.700312 88.3025935,110.073173 C73.611854,95.4654658 64.8677898,78.3885437 66.803227,57.2292132 C68.1712787,42.2715849 74.0527146,29.3462646 84.8033863,18.7517722 C105.393354,-1.53572199 136.805164,-4.82141828 161.048542,10.7510424 C184.333097,25.7086706 194.996783,54.8450075 185.906752,79.7822957 C179.052655,77.9239597 172.151111,76.049808 164.563565,73.9917997 C167.418285,60.1274266 165.306899,47.6765751 155.95591,37.0109123 C149.777932,29.9690049 141.850349,26.2780332 132.835442,24.9178894 C114.764113,22.1877169 97.0209573,33.7983633 91.7563309,51.5355878 C85.7800012,71.6669027 94.8245623,88.1111998 119.540432,100.502743 L119.540432,100.502743 Z" fill="#C73A63"></path><path d="M149.841194,79.4106285 C157.316054,92.5969067 164.905578,105.982857 172.427885,119.246236 C210.44865,107.483365 239.114472,128.530009 249.398582,151.063322 C261.81978,178.282014 253.328765,210.520191 228.933162,227.312431 C203.893073,244.551464 172.226236,241.605803 150.040866,219.46195 C155.694953,214.729124 161.376716,209.974552 167.44794,204.895759 C189.360489,219.088306 208.525074,218.420096 222.753207,201.614016 C234.885769,187.277151 234.622834,165.900356 222.138374,151.863988 C207.730339,135.66681 188.431321,135.172572 165.103273,150.721309 C155.426087,133.553447 145.58086,116.521995 136.210101,99.2295848 C133.05093,93.4015266 129.561608,90.0209366 122.440622,88.7873178 C110.547271,86.7253555 102.868785,76.5124151 102.408155,65.0698097 C101.955433,53.7537294 108.621719,43.5249733 119.04224,39.5394355 C129.363912,35.5914599 141.476705,38.7783085 148.419765,47.554004 C154.093621,54.7244134 155.896602,62.7943365 152.911402,71.6372484 C152.081082,74.1025091 151.00562,76.4886916 149.841194,79.4106285 L149.841194,79.4106285 Z" fill="#4B4B4B"></path><path d="M167.706921,187.209935 L121.936499,187.209935 C117.54964,205.253587 108.074103,219.821756 91.7464461,229.085759 C79.0544063,236.285822 65.3738898,238.72736 50.8136292,236.376762 C24.0061432,232.053165 2.08568567,207.920497 0.156179306,180.745298 C-2.02835403,149.962159 19.1309765,122.599149 47.3341915,116.452801 C49.2814904,123.524363 51.2485589,130.663141 53.1958579,137.716911 C27.3195169,150.919004 18.3639187,167.553089 25.6054984,188.352614 C31.9811726,206.657224 50.0900643,216.690262 69.7528413,212.809503 C89.8327554,208.847688 99.9567329,192.160226 98.7211371,165.37844 C117.75722,165.37844 136.809118,165.180745 155.847178,165.475311 C163.280522,165.591951 169.019617,164.820939 174.620326,158.267339 C183.840836,147.48306 200.811003,148.455721 210.741239,158.640984 C220.88894,169.049642 220.402609,185.79839 209.663799,195.768166 C199.302587,205.38802 182.933414,204.874012 173.240413,194.508846 C171.247644,192.37176 169.677943,189.835329 167.706921,187.209935 L167.706921,187.209935 Z" fill="#4A4A4A"></path></g></svg>'''
272 263
273 264 valid_events = [
274 265 events.PullRequestCloseEvent,
275 266 events.PullRequestMergeEvent,
276 267 events.PullRequestUpdateEvent,
277 268 events.PullRequestCommentEvent,
278 269 events.PullRequestReviewEvent,
279 270 events.PullRequestCreateEvent,
280 271 events.RepoPushEvent,
281 272 events.RepoCreateEvent,
282 273 ]
283 274
284 275 def settings_schema(self):
285 276 schema = WebhookSettingsSchema()
286 277 schema.add(colander.SchemaNode(
287 278 colander.Set(),
288 279 widget=deform.widget.CheckboxChoiceWidget(
289 280 values=sorted(
290 281 [(e.name, e.display_name) for e in self.valid_events]
291 282 )
292 283 ),
293 284 description="Events activated for this integration",
294 285 name='events'
295 286 ))
296 287 return schema
297 288
298 289 def send_event(self, event):
299 290 log.debug('handling event %s with Webhook integration %s',
300 291 event.name, self)
301 292
302 293 if event.__class__ not in self.valid_events:
303 294 log.debug('event not valid: %r' % event)
304 295 return
305 296
306 297 if event.name not in self.settings['events']:
307 298 log.debug('event ignored: %r' % event)
308 299 return
309 300
310 301 data = event.as_dict()
311 302 template_url = self.settings['url']
312 303
313 304 headers = {}
314 305 head_key = self.settings.get('custom_header_key')
315 306 head_val = self.settings.get('custom_header_val')
316 307 if head_key and head_val:
317 308 headers = {head_key: head_val}
318 309
319 310 handler = WebhookHandler(
320 311 template_url, self.settings['secret_token'], headers)
321 312
322 313 url_calls = handler(event, data)
323 314 log.debug('webhook: calling following urls: %s',
324 315 [x[0] for x in url_calls])
325 316
326 317 run_task(post_to_webhook, url_calls, self.settings)
327 318
328 319
329 320 @async_task(ignore_result=True, base=RequestContextTask)
330 321 def post_to_webhook(url_calls, settings):
331 322 """
332 323 Example data::
333 324
334 325 {'actor': {'user_id': 2, 'username': u'admin'},
335 326 'actor_ip': u'192.168.157.1',
336 327 'name': 'repo-push',
337 328 'push': {'branches': [{'name': u'default',
338 329 'url': 'http://rc.local:8080/hg-repo/changelog?branch=default'}],
339 330 'commits': [{'author': u'Marcin Kuzminski <marcin@rhodecode.com>',
340 331 'branch': u'default',
341 332 'date': datetime.datetime(2017, 11, 30, 12, 59, 48),
342 333 'issues': [],
343 334 'mentions': [],
344 335 'message': u'commit Thu 30 Nov 2017 13:59:48 CET',
345 336 'message_html': u'commit Thu 30 Nov 2017 13:59:48 CET',
346 337 'message_html_title': u'commit Thu 30 Nov 2017 13:59:48 CET',
347 338 'parents': [{'raw_id': '431b772a5353dad9974b810dd3707d79e3a7f6e0'}],
348 339 'permalink_url': u'http://rc.local:8080/_7/changeset/a815cc738b9651eb5ffbcfb1ce6ccd7c701a5ddf',
349 340 'raw_id': 'a815cc738b9651eb5ffbcfb1ce6ccd7c701a5ddf',
350 341 'refs': {'bookmarks': [], 'branches': [u'default'], 'tags': [u'tip']},
351 342 'reviewers': [],
352 343 'revision': 9L,
353 344 'short_id': 'a815cc738b96',
354 345 'url': u'http://rc.local:8080/hg-repo/changeset/a815cc738b9651eb5ffbcfb1ce6ccd7c701a5ddf'}],
355 346 'issues': {}},
356 347 'repo': {'extra_fields': '',
357 348 'permalink_url': u'http://rc.local:8080/_7',
358 349 'repo_id': 7,
359 350 'repo_name': u'hg-repo',
360 351 'repo_type': u'hg',
361 352 'url': u'http://rc.local:8080/hg-repo'},
362 353 'server_url': u'http://rc.local:8080',
363 354 'utc_timestamp': datetime.datetime(2017, 11, 30, 13, 0, 1, 569276)
364 355
365 356 """
366 357 max_retries = 3
367 358 retries = Retry(
368 359 total=max_retries,
369 360 backoff_factor=0.15,
370 361 status_forcelist=[500, 502, 503, 504])
371 362 call_headers = {
372 363 'User-Agent': 'RhodeCode-webhook-caller/{}'.format(
373 364 rhodecode.__version__)
374 365 } # updated below with custom ones, allows override
375 366
376 367 for url, token, headers, data in url_calls:
377 368 req_session = requests.Session()
378 369 req_session.mount( # retry max N times
379 370 'http://', requests.adapters.HTTPAdapter(max_retries=retries))
380 371
381 372 method = settings.get('method_type') or 'post'
382 373 call_method = getattr(req_session, method)
383 374
384 375 headers = headers or {}
385 376 call_headers.update(headers)
386 377 auth = get_auth(settings)
387 378
388 379 log.debug('calling Webhook with method: %s, and auth:%s',
389 380 call_method, auth)
390 381 if settings.get('log_data'):
391 382 log.debug('calling webhook with data: %s', data)
392 383 resp = call_method(url, json={
393 384 'token': token,
394 385 'event': data
395 386 }, headers=call_headers, auth=auth)
396 387 log.debug('Got Webhook response: %s', resp)
397 388
398 389 resp.raise_for_status() # raise exception on a failed request
General Comments 0
You need to be logged in to leave comments. Login now