##// END OF EJS Templates
webhook: use textarea and format url vars better for webhook integration....
marcink -
r2584:b8f98b31 default
parent child Browse files
Show More
@@ -1,154 +1,157 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 114 super(EEIntegration, self).__init__(settings)
115 115
116 116
117 117 # Helpers #
118
119 # common vars for url template
120 CI_URL_VARS = [
118 WEBHOOK_URL_VARS = [
121 119 ('event_name', 'Unique name of the event type, e.g pullrequest-update'),
122 120 ('repo_name', 'Full name of the repository'),
123 121 ('repo_type', 'VCS type of repository'),
124 122 ('repo_id', 'Unique id of repository'),
125 123 ('repo_url', 'Repository url'),
126 124 # extra repo fields
127 125 ('extra:<extra_key_name>', 'Extra repo variables, read from its settings.'),
128 126
129 127 # special attrs below that we handle, using multi-call
130 128 ('branch', 'Name of each brach submitted, if any.'),
131 129 ('commit_id', 'Id of each commit submitted, if any.'),
132 130
133 131 # pr events vars
134 132 ('pull_request_id', 'Unique ID of the pull request.'),
135 133 ('pull_request_url', 'Pull request url.'),
136 134 ('pull_request_shadow_url', 'Pull request shadow repo clone url.'),
137 135
138 136 # user who triggers the call
139 137 ('username', 'User who triggered the call.'),
140 138 ('user_id', 'User id who triggered the call.'),
141 139 ]
142 140
141 # common vars for url template used for CI plugins. Shared with webhook
142 CI_URL_VARS = WEBHOOK_URL_VARS
143
143 144
144 145 def get_auth(settings):
145 146 from requests.auth import HTTPBasicAuth
146 147 username = settings.get('username')
147 148 password = settings.get('password')
148 149 if username and password:
149 150 return HTTPBasicAuth(username, password)
150 151 return None
151 152
152 153
153 154 def get_url_vars(url_vars):
154 return ', '.join('${' + key + '}' for key, explanation in url_vars)
155 return '\n'.join(
156 '{} - {}'.format('${' + key + '}', explanation)
157 for key, explanation in url_vars)
@@ -1,397 +1,381 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 import collections
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 36 from rhodecode.integrations.types.base import (
37 IntegrationTypeBase, get_auth, get_url_vars)
37 IntegrationTypeBase, get_auth, get_url_vars, WEBHOOK_URL_VARS)
38 38 from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask
39 from rhodecode.model.validation_schema import widgets
39 40
40 41 log = logging.getLogger(__name__)
41 42
42 43
43 44 # updating this required to update the `common_vars` passed in url calling func
44 WEBHOOK_URL_VARS = [
45 ('event_name', 'Unique name of the event type, e.g pullrequest-update'),
46 ('repo_name', 'Full name of the repository'),
47 ('repo_type', 'VCS type of repository'),
48 ('repo_id', 'Unique id of repository'),
49 ('repo_url', 'Repository url'),
50 # extra repo fields
51 ('extra:<extra_key_name>', 'Extra repo variables, read from its settings.'),
52 45
53 # special attrs below that we handle, using multi-call
54 ('branch', 'Name of each brach submitted, if any.'),
55 ('commit_id', 'Id of each commit submitted, if any.'),
56
57 # pr events vars
58 ('pull_request_id', 'Unique ID of the pull request.'),
59 ('pull_request_url', 'Pull request url.'),
60 ('pull_request_shadow_url', 'Pull request shadow repo clone url.'),
61
62 # user who triggers the call
63 ('username', 'User who triggered the call.'),
64 ('user_id', 'User id who triggered the call.'),
65 ]
66 46 URL_VARS = get_url_vars(WEBHOOK_URL_VARS)
67 47
68 48
69 49 class WebhookHandler(object):
70 50 def __init__(self, template_url, secret_token, headers):
71 51 self.template_url = template_url
72 52 self.secret_token = secret_token
73 53 self.headers = headers
74 54
75 55 def get_base_parsed_template(self, data):
76 56 """
77 57 initially parses the passed in template with some common variables
78 58 available on ALL calls
79 59 """
80 60 # note: make sure to update the `WEBHOOK_URL_VARS` if this changes
81 61 common_vars = {
82 62 'repo_name': data['repo']['repo_name'],
83 63 'repo_type': data['repo']['repo_type'],
84 64 'repo_id': data['repo']['repo_id'],
85 65 'repo_url': data['repo']['url'],
86 66 'username': data['actor']['username'],
87 67 'user_id': data['actor']['user_id'],
88 68 'event_name': data['name']
89 69 }
90 70
91 71 extra_vars = {}
92 72 for extra_key, extra_val in data['repo']['extra_fields'].items():
93 73 extra_vars['extra__{}'.format(extra_key)] = extra_val
94 74 common_vars.update(extra_vars)
95 75
96 76 template_url = self.template_url.replace('${extra:', '${extra__')
97 77 return string.Template(template_url).safe_substitute(**common_vars)
98 78
99 79 def repo_push_event_handler(self, event, data):
100 80 url = self.get_base_parsed_template(data)
101 81 url_cals = []
102 82 branch_data = collections.OrderedDict()
103 83 for obj in data['push']['branches']:
104 84 branch_data[obj['name']] = obj
105 85
106 86 branches_commits = collections.OrderedDict()
107 87 for commit in data['push']['commits']:
108 88 if commit.get('git_ref_change'):
109 89 # special case for GIT that allows creating tags,
110 90 # deleting branches without associated commit
111 91 continue
112 92
113 93 if commit['branch'] not in branches_commits:
114 94 branch_commits = {'branch': branch_data[commit['branch']],
115 95 'commits': []}
116 96 branches_commits[commit['branch']] = branch_commits
117 97
118 98 branch_commits = branches_commits[commit['branch']]
119 99 branch_commits['commits'].append(commit)
120 100
121 101 if '${branch}' in url:
122 102 # call it multiple times, for each branch if used in variables
123 103 for branch, commit_ids in branches_commits.items():
124 104 branch_url = string.Template(url).safe_substitute(branch=branch)
125 105 # call further down for each commit if used
126 106 if '${commit_id}' in branch_url:
127 107 for commit_data in commit_ids['commits']:
128 108 commit_id = commit_data['raw_id']
129 109 commit_url = string.Template(branch_url).safe_substitute(
130 110 commit_id=commit_id)
131 111 # register per-commit call
132 112 log.debug(
133 113 'register webhook call(%s) to url %s', event, commit_url)
134 114 url_cals.append((commit_url, self.secret_token, self.headers, data))
135 115
136 116 else:
137 117 # register per-branch call
138 118 log.debug(
139 119 'register webhook call(%s) to url %s', event, branch_url)
140 120 url_cals.append((branch_url, self.secret_token, self.headers, data))
141 121
142 122 else:
143 123 log.debug(
144 124 'register webhook call(%s) to url %s', event, url)
145 125 url_cals.append((url, self.secret_token, self.headers, data))
146 126
147 127 return url_cals
148 128
149 129 def repo_create_event_handler(self, event, data):
150 130 url = self.get_base_parsed_template(data)
151 131 log.debug(
152 132 'register webhook call(%s) to url %s', event, url)
153 133 return [(url, self.secret_token, self.headers, data)]
154 134
155 135 def pull_request_event_handler(self, event, data):
156 136 url = self.get_base_parsed_template(data)
157 137 log.debug(
158 138 'register webhook call(%s) to url %s', event, url)
159 139 url = string.Template(url).safe_substitute(
160 140 pull_request_id=data['pullrequest']['pull_request_id'],
161 141 pull_request_url=data['pullrequest']['url'],
162 142 pull_request_shadow_url=data['pullrequest']['shadow_url'],)
163 143 return [(url, self.secret_token, self.headers, data)]
164 144
165 145 def __call__(self, event, data):
166 146 if isinstance(event, events.RepoPushEvent):
167 147 return self.repo_push_event_handler(event, data)
168 148 elif isinstance(event, events.RepoCreateEvent):
169 149 return self.repo_create_event_handler(event, data)
170 150 elif isinstance(event, events.PullRequestEvent):
171 151 return self.pull_request_event_handler(event, data)
172 152 else:
173 153 raise ValueError('event type not supported: %s' % events)
174 154
175 155
176 156 class WebhookSettingsSchema(colander.Schema):
177 157 url = colander.SchemaNode(
178 158 colander.String(),
179 159 title=_('Webhook URL'),
180 160 description=
181 _('URL to which Webhook should submit data. Following variables '
182 'are allowed to be used: {vars}. Some of the variables would '
183 'trigger multiple calls, like ${{branch}} or ${{commit_id}}. '
184 'Webhook will be called as many times as unique objects in '
185 'data in such cases.').format(vars=URL_VARS),
161 _('URL to which Webhook should submit data. If used some of the '
162 'variables would trigger multiple calls, like ${branch} or '
163 '${commit_id}. Webhook will be called as many times as unique '
164 'objects in data in such cases.'),
186 165 missing=colander.required,
187 166 required=True,
188 167 validator=colander.url,
189 widget=deform.widget.TextInputWidget(
190 placeholder='https://www.example.com/webhook'
191 ),
168 widget=widgets.CodeMirrorWidget(
169 help_block_collapsable_name='Show url variables',
170 help_block_collapsable=(
171 'E.g http://my-serv/trigger_job/${{event_name}}'
172 '?PR_ID=${{pull_request_id}}'
173 '\nFull list of vars:\n{}'.format(URL_VARS)),
174 codemirror_mode='text',
175 codemirror_options='{"lineNumbers": false, "lineWrapping": true}'),
192 176 )
193 177 secret_token = colander.SchemaNode(
194 178 colander.String(),
195 179 title=_('Secret Token'),
196 180 description=_('Optional string used to validate received payloads. '
197 181 'It will be sent together with event data in JSON'),
198 182 default='',
199 183 missing='',
200 184 widget=deform.widget.TextInputWidget(
201 185 placeholder='e.g. secret_token'
202 186 ),
203 187 )
204 188 username = colander.SchemaNode(
205 189 colander.String(),
206 190 title=_('Username'),
207 191 description=_('Optional username to authenticate the call.'),
208 192 default='',
209 193 missing='',
210 194 widget=deform.widget.TextInputWidget(
211 195 placeholder='e.g. admin'
212 196 ),
213 197 )
214 198 password = colander.SchemaNode(
215 199 colander.String(),
216 200 title=_('Password'),
217 201 description=_('Optional password to authenticate the call.'),
218 202 default='',
219 203 missing='',
220 204 widget=deform.widget.PasswordWidget(
221 205 placeholder='e.g. secret.',
222 206 redisplay=True,
223 207 ),
224 208 )
225 209 custom_header_key = colander.SchemaNode(
226 210 colander.String(),
227 211 title=_('Custom Header Key'),
228 212 description=_('Custom Header name to be set when calling endpoint.'),
229 213 default='',
230 214 missing='',
231 215 widget=deform.widget.TextInputWidget(
232 placeholder='e.g.Authorization'
216 placeholder='e.g: Authorization'
233 217 ),
234 218 )
235 219 custom_header_val = colander.SchemaNode(
236 220 colander.String(),
237 221 title=_('Custom Header Value'),
238 222 description=_('Custom Header value to be set when calling endpoint.'),
239 223 default='',
240 224 missing='',
241 225 widget=deform.widget.TextInputWidget(
242 placeholder='e.g. RcLogin auth=xxxx'
226 placeholder='e.g. Basic XxXxXx'
243 227 ),
244 228 )
245 229 method_type = colander.SchemaNode(
246 230 colander.String(),
247 231 title=_('Call Method'),
248 232 description=_('Select if the Webhook call should be made '
249 233 'with POST or GET.'),
250 234 default='post',
251 235 missing='',
252 236 widget=deform.widget.RadioChoiceWidget(
253 237 values=[('get', 'GET'), ('post', 'POST')],
254 238 inline=True
255 239 ),
256 240 )
257 241
258 242
259 243 class WebhookIntegrationType(IntegrationTypeBase):
260 244 key = 'webhook'
261 245 display_name = _('Webhook')
262 246 description = _('Post json events to a Webhook endpoint')
263 247
264 248 @classmethod
265 249 def icon(cls):
266 250 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>'''
267 251
268 252 valid_events = [
269 253 events.PullRequestCloseEvent,
270 254 events.PullRequestMergeEvent,
271 255 events.PullRequestUpdateEvent,
272 256 events.PullRequestCommentEvent,
273 257 events.PullRequestReviewEvent,
274 258 events.PullRequestCreateEvent,
275 259 events.RepoPushEvent,
276 260 events.RepoCreateEvent,
277 261 ]
278 262
279 263 def settings_schema(self):
280 264 schema = WebhookSettingsSchema()
281 265 schema.add(colander.SchemaNode(
282 266 colander.Set(),
283 267 widget=deform.widget.CheckboxChoiceWidget(
284 268 values=sorted(
285 269 [(e.name, e.display_name) for e in self.valid_events]
286 270 )
287 271 ),
288 272 description="Events activated for this integration",
289 273 name='events'
290 274 ))
291 275 return schema
292 276
293 277 def send_event(self, event):
294 278 log.debug('handling event %s with Webhook integration %s',
295 279 event.name, self)
296 280
297 281 if event.__class__ not in self.valid_events:
298 282 log.debug('event not valid: %r' % event)
299 283 return
300 284
301 285 if event.name not in self.settings['events']:
302 286 log.debug('event ignored: %r' % event)
303 287 return
304 288
305 289 data = event.as_dict()
306 290 template_url = self.settings['url']
307 291
308 292 headers = {}
309 293 head_key = self.settings.get('custom_header_key')
310 294 head_val = self.settings.get('custom_header_val')
311 295 if head_key and head_val:
312 296 headers = {head_key: head_val}
313 297
314 298 handler = WebhookHandler(
315 299 template_url, self.settings['secret_token'], headers)
316 300
317 301 url_calls = handler(event, data)
318 302 log.debug('webhook: calling following urls: %s',
319 303 [x[0] for x in url_calls])
320 304
321 305 run_task(post_to_webhook, url_calls, self.settings)
322 306
323 307
324 308 @async_task(ignore_result=True, base=RequestContextTask)
325 309 def post_to_webhook(url_calls, settings):
326 310 """
327 311 Example data::
328 312
329 313 {'actor': {'user_id': 2, 'username': u'admin'},
330 314 'actor_ip': u'192.168.157.1',
331 315 'name': 'repo-push',
332 316 'push': {'branches': [{'name': u'default',
333 317 'url': 'http://rc.local:8080/hg-repo/changelog?branch=default'}],
334 318 'commits': [{'author': u'Marcin Kuzminski <marcin@rhodecode.com>',
335 319 'branch': u'default',
336 320 'date': datetime.datetime(2017, 11, 30, 12, 59, 48),
337 321 'issues': [],
338 322 'mentions': [],
339 323 'message': u'commit Thu 30 Nov 2017 13:59:48 CET',
340 324 'message_html': u'commit Thu 30 Nov 2017 13:59:48 CET',
341 325 'message_html_title': u'commit Thu 30 Nov 2017 13:59:48 CET',
342 326 'parents': [{'raw_id': '431b772a5353dad9974b810dd3707d79e3a7f6e0'}],
343 327 'permalink_url': u'http://rc.local:8080/_7/changeset/a815cc738b9651eb5ffbcfb1ce6ccd7c701a5ddf',
344 328 'raw_id': 'a815cc738b9651eb5ffbcfb1ce6ccd7c701a5ddf',
345 329 'refs': {'bookmarks': [], 'branches': [u'default'], 'tags': [u'tip']},
346 330 'reviewers': [],
347 331 'revision': 9L,
348 332 'short_id': 'a815cc738b96',
349 333 'url': u'http://rc.local:8080/hg-repo/changeset/a815cc738b9651eb5ffbcfb1ce6ccd7c701a5ddf'}],
350 334 'issues': {}},
351 335 'repo': {'extra_fields': '',
352 336 'permalink_url': u'http://rc.local:8080/_7',
353 337 'repo_id': 7,
354 338 'repo_name': u'hg-repo',
355 339 'repo_type': u'hg',
356 340 'url': u'http://rc.local:8080/hg-repo'},
357 341 'server_url': u'http://rc.local:8080',
358 342 'utc_timestamp': datetime.datetime(2017, 11, 30, 13, 0, 1, 569276)
359 343
360 344 """
361 345 max_retries = 3
362 346 retries = Retry(
363 347 total=max_retries,
364 348 backoff_factor=0.15,
365 349 status_forcelist=[500, 502, 503, 504])
366 350 call_headers = {
367 351 'User-Agent': 'RhodeCode-webhook-caller/{}'.format(
368 352 rhodecode.__version__)
369 353 } # updated below with custom ones, allows override
370 354
371 355 auth = get_auth(settings)
372 356 for url, token, headers, data in url_calls:
373 357 req_session = requests.Session()
374 358 req_session.mount( # retry max N times
375 359 'http://', requests.adapters.HTTPAdapter(max_retries=retries))
376 360
377 361 method = settings.get('method_type') or 'post'
378 362 call_method = getattr(req_session, method)
379 363
380 364 headers = headers or {}
381 365 call_headers.update(headers)
382 366
383 367 log.debug('calling Webhook with method: %s, and auth:%s',
384 368 call_method, auth)
385 369 if settings.get('log_data'):
386 370 log.debug('calling webhook with data: %s', data)
387 371 resp = call_method(url, json={
388 372 'token': token,
389 373 'event': data
390 374 }, headers=call_headers, auth=auth)
391 375 log.debug('Got Webhook response: %s', resp)
392 376
393 377 try:
394 378 resp.raise_for_status() # raise exception on a failed request
395 379 except Exception:
396 380 log.error(resp.text)
397 381 raise
@@ -1,30 +1,35 b''
1 1 <div tal:define="rows rows|field.widget.rows;
2 2 cols cols|field.widget.cols;
3 3 css_class css_class|field.widget.css_class;
4 4 oid oid|field.oid;
5 5 name name|field.name;
6 6 style style|field.widget.style;
7 7 help_block help_block|field.widget.help_block|'';
8 help_block_collapsable_name help_block_collapsable_name|field.widget.help_block_collapsable_name|'';
9 help_block_collapsable help_block_collapsable|field.widget.help_block_collapsable|'';
8 10 codemirror_options codemirror_options|field.widget.codemirror_options|{};
9 11 codemirror_mode codemirror_mode|field.widget.codemirror_mode|''
10 12 ">
11 13
12 14 <textarea tal:attributes="rows rows;
13 15 cols cols;
14 16 class string: form-control ${css_class or ''};
15 17 style style"
16 18 id="${oid}"
17 19 name="${name}">${cstruct}</textarea>
18 20
19 21 <p tal:condition="help_block" class="help-block">${help_block}</p>
22 <span tal:condition="help_block_collapsable" class="help-block pre-formatting"><a href="#showVars" onclick="$('#help_block_${oid}').toggle(); return false">${help_block_collapsable_name}</a>
23 <p id="help_block_${oid}" style="display: none">${help_block_collapsable}</p>
24 </span>
20 25 <script type="text/javascript">
21 26 deform.addCallback(
22 27 '${oid}',
23 28 function(oid) {
24 29 var myCodeMirror = initCodeMirror(oid, '', false, ${codemirror_options});
25 30 setCodeMirrorMode(myCodeMirror, '${codemirror_mode}');
26 31 }
27 32 );
28 33 </script>
29 34
30 35 </div>
General Comments 0
You need to be logged in to leave comments. Login now