##// END OF EJS Templates
integrations: added branch_head into URL variables. This allow triggering more explicit...
dan -
r2864:9b105d43 stable
parent child Browse files
Show More
@@ -1,311 +1,322 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 import string
23 23 import collections
24 24 import logging
25 25
26 26 from mako import exceptions
27 27
28 28 from rhodecode.translation import _
29 29
30 30
31 31 log = logging.getLogger(__name__)
32 32
33 33
34 34 class IntegrationTypeBase(object):
35 35 """ Base class for IntegrationType plugins """
36 36 is_dummy = False
37 37 description = ''
38 38
39 39 @classmethod
40 40 def icon(cls):
41 41 return '''
42 42 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
43 43 <svg
44 44 xmlns:dc="http://purl.org/dc/elements/1.1/"
45 45 xmlns:cc="http://creativecommons.org/ns#"
46 46 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
47 47 xmlns:svg="http://www.w3.org/2000/svg"
48 48 xmlns="http://www.w3.org/2000/svg"
49 49 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
50 50 xmlns:inkscape="http://setwww.inkscape.org/namespaces/inkscape"
51 51 viewBox="0 -256 1792 1792"
52 52 id="svg3025"
53 53 version="1.1"
54 54 inkscape:version="0.48.3.1 r9886"
55 55 width="100%"
56 56 height="100%"
57 57 sodipodi:docname="cog_font_awesome.svg">
58 58 <metadata
59 59 id="metadata3035">
60 60 <rdf:RDF>
61 61 <cc:Work
62 62 rdf:about="">
63 63 <dc:format>image/svg+xml</dc:format>
64 64 <dc:type
65 65 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
66 66 </cc:Work>
67 67 </rdf:RDF>
68 68 </metadata>
69 69 <defs
70 70 id="defs3033" />
71 71 <sodipodi:namedview
72 72 pagecolor="#ffffff"
73 73 bordercolor="#666666"
74 74 borderopacity="1"
75 75 objecttolerance="10"
76 76 gridtolerance="10"
77 77 guidetolerance="10"
78 78 inkscape:pageopacity="0"
79 79 inkscape:pageshadow="2"
80 80 inkscape:window-width="640"
81 81 inkscape:window-height="480"
82 82 id="namedview3031"
83 83 showgrid="false"
84 84 inkscape:zoom="0.13169643"
85 85 inkscape:cx="896"
86 86 inkscape:cy="896"
87 87 inkscape:window-x="0"
88 88 inkscape:window-y="25"
89 89 inkscape:window-maximized="0"
90 90 inkscape:current-layer="svg3025" />
91 91 <g
92 92 transform="matrix(1,0,0,-1,121.49153,1285.4237)"
93 93 id="g3027">
94 94 <path
95 95 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"
96 96 id="path3029"
97 97 inkscape:connector-curvature="0"
98 98 style="fill:currentColor" />
99 99 </g>
100 100 </svg>
101 101 '''
102 102
103 103 def __init__(self, settings):
104 104 """
105 105 :param settings: dict of settings to be used for the integration
106 106 """
107 107 self.settings = settings
108 108
109 109 def settings_schema(self):
110 110 """
111 111 A colander schema of settings for the integration type
112 112 """
113 113 return colander.Schema()
114 114
115 115
116 116 class EEIntegration(IntegrationTypeBase):
117 117 description = 'Integration available in RhodeCode EE edition.'
118 118 is_dummy = True
119 119
120 120 def __init__(self, name, key, settings=None):
121 121 self.display_name = name
122 122 self.key = key
123 123 super(EEIntegration, self).__init__(settings)
124 124
125 125
126 126 # Helpers #
127 # updating this required to update the `common_vars` as well.
127 128 WEBHOOK_URL_VARS = [
128 129 ('event_name', 'Unique name of the event type, e.g pullrequest-update'),
129 130 ('repo_name', 'Full name of the repository'),
130 131 ('repo_type', 'VCS type of repository'),
131 132 ('repo_id', 'Unique id of repository'),
132 133 ('repo_url', 'Repository url'),
133 134 # extra repo fields
134 135 ('extra:<extra_key_name>', 'Extra repo variables, read from its settings.'),
135 136
136 137 # special attrs below that we handle, using multi-call
137 ('branch', 'Name of each brach submitted, if any.'),
138 ('commit_id', 'Id of each commit submitted, if any.'),
138 ('branch', 'Name of each branch submitted, if any.'),
139 ('branch_head', 'Head ID of pushed branch (full sha of last commit), if any.'),
140 ('commit_id', 'ID (full sha) of each commit submitted, if any.'),
139 141
140 142 # pr events vars
141 143 ('pull_request_id', 'Unique ID of the pull request.'),
142 144 ('pull_request_title', 'Title of the pull request.'),
143 145 ('pull_request_url', 'Pull request url.'),
144 146 ('pull_request_shadow_url', 'Pull request shadow repo clone url.'),
145 147 ('pull_request_commits_uid', 'Calculated UID of all commits inside the PR. '
146 148 'Changes after PR update'),
147 149
148 150 # user who triggers the call
149 151 ('username', 'User who triggered the call.'),
150 152 ('user_id', 'User id who triggered the call.'),
151 153 ]
152 154
153 155 # common vars for url template used for CI plugins. Shared with webhook
154 156 CI_URL_VARS = WEBHOOK_URL_VARS
155 157
156 158
157 159 class CommitParsingDataHandler(object):
158 160
159 161 def aggregate_branch_data(self, branches, commits):
160 162 branch_data = collections.OrderedDict()
161 163 for obj in branches:
162 164 branch_data[obj['name']] = obj
163 165
164 166 branches_commits = collections.OrderedDict()
165 167 for commit in commits:
166 168 if commit.get('git_ref_change'):
167 169 # special case for GIT that allows creating tags,
168 170 # deleting branches without associated commit
169 171 continue
170 172 commit_branch = commit['branch']
171 173
172 174 if commit_branch not in branches_commits:
173 175 _branch = branch_data[commit_branch] \
174 176 if commit_branch else commit_branch
175 177 branch_commits = {'branch': _branch,
178 'branch_head': '',
176 179 'commits': []}
177 180 branches_commits[commit_branch] = branch_commits
178 181
179 182 branch_commits = branches_commits[commit_branch]
180 183 branch_commits['commits'].append(commit)
184 branch_commits['branch_head'] = commit['raw_id']
181 185 return branches_commits
182 186
183 187
184 188 class WebhookDataHandler(CommitParsingDataHandler):
185 189 name = 'webhook'
186 190
187 191 def __init__(self, template_url, headers):
188 192 self.template_url = template_url
189 193 self.headers = headers
190 194
191 195 def get_base_parsed_template(self, data):
192 196 """
193 197 initially parses the passed in template with some common variables
194 198 available on ALL calls
195 199 """
196 200 # note: make sure to update the `WEBHOOK_URL_VARS` if this changes
197 201 common_vars = {
198 202 'repo_name': data['repo']['repo_name'],
199 203 'repo_type': data['repo']['repo_type'],
200 204 'repo_id': data['repo']['repo_id'],
201 205 'repo_url': data['repo']['url'],
202 206 'username': data['actor']['username'],
203 207 'user_id': data['actor']['user_id'],
204 208 'event_name': data['name']
205 209 }
206 210
207 211 extra_vars = {}
208 212 for extra_key, extra_val in data['repo']['extra_fields'].items():
209 213 extra_vars['extra__{}'.format(extra_key)] = extra_val
210 214 common_vars.update(extra_vars)
211 215
212 216 template_url = self.template_url.replace('${extra:', '${extra__')
213 217 return string.Template(template_url).safe_substitute(**common_vars)
214 218
215 219 def repo_push_event_handler(self, event, data):
216 220 url = self.get_base_parsed_template(data)
217 221 url_cals = []
218 222
219 223 branches_commits = self.aggregate_branch_data(
220 224 data['push']['branches'], data['push']['commits'])
221 if '${branch}' in url:
225 if '${branch}' in url or '${branch_head}' in url:
222 226 # call it multiple times, for each branch if used in variables
223 227 for branch, commit_ids in branches_commits.items():
224 228 branch_url = string.Template(url).safe_substitute(branch=branch)
229
230 if '${branch_head}' in branch_url:
231 # last commit in the aggregate is the head of the branch
232 branch_head = commit_ids['branch_head']
233 branch_url = string.Template(branch_url).safe_substitute(
234 branch_head=branch_head)
235
225 236 # call further down for each commit if used
226 237 if '${commit_id}' in branch_url:
227 238 for commit_data in commit_ids['commits']:
228 239 commit_id = commit_data['raw_id']
229 240 commit_url = string.Template(branch_url).safe_substitute(
230 241 commit_id=commit_id)
231 242 # register per-commit call
232 243 log.debug(
233 244 'register %s call(%s) to url %s',
234 245 self.name, event, commit_url)
235 246 url_cals.append(
236 247 (commit_url, self.headers, data))
237 248
238 249 else:
239 250 # register per-branch call
240 251 log.debug(
241 252 'register %s call(%s) to url %s',
242 253 self.name, event, branch_url)
243 254 url_cals.append(
244 255 (branch_url, self.headers, data))
245 256
246 257 else:
247 258 log.debug(
248 259 'register %s call(%s) to url %s', self.name, event, url)
249 260 url_cals.append((url, self.headers, data))
250 261
251 262 return url_cals
252 263
253 264 def repo_create_event_handler(self, event, data):
254 265 url = self.get_base_parsed_template(data)
255 266 log.debug(
256 267 'register %s call(%s) to url %s', self.name, event, url)
257 268 return [(url, self.headers, data)]
258 269
259 270 def pull_request_event_handler(self, event, data):
260 271 url = self.get_base_parsed_template(data)
261 272 log.debug(
262 273 'register %s call(%s) to url %s', self.name, event, url)
263 274 url = string.Template(url).safe_substitute(
264 275 pull_request_id=data['pullrequest']['pull_request_id'],
265 276 pull_request_title=data['pullrequest']['title'],
266 277 pull_request_url=data['pullrequest']['url'],
267 278 pull_request_shadow_url=data['pullrequest']['shadow_url'],
268 279 pull_request_commits_uid=data['pullrequest']['commits_uid'],
269 280 )
270 281 return [(url, self.headers, data)]
271 282
272 283 def __call__(self, event, data):
273 284 from rhodecode import events
274 285
275 286 if isinstance(event, events.RepoPushEvent):
276 287 return self.repo_push_event_handler(event, data)
277 288 elif isinstance(event, events.RepoCreateEvent):
278 289 return self.repo_create_event_handler(event, data)
279 290 elif isinstance(event, events.PullRequestEvent):
280 291 return self.pull_request_event_handler(event, data)
281 292 else:
282 293 raise ValueError(
283 294 'event type `%s` not in supported list: %s' % (
284 295 event.__class__, events))
285 296
286 297
287 298 def get_auth(settings):
288 299 from requests.auth import HTTPBasicAuth
289 300 username = settings.get('username')
290 301 password = settings.get('password')
291 302 if username and password:
292 303 return HTTPBasicAuth(username, password)
293 304 return None
294 305
295 306
296 307 def get_web_token(settings):
297 308 return settings['secret_token']
298 309
299 310
300 311 def get_url_vars(url_vars):
301 312 return '\n'.join(
302 313 '{} - {}'.format('${' + key + '}', explanation)
303 314 for key, explanation in url_vars)
304 315
305 316
306 317 def render_with_traceback(template, *args, **kwargs):
307 318 try:
308 319 return template.render(*args, **kwargs)
309 320 except Exception:
310 321 log.error(exceptions.text_error_template().render())
311 322 raise
General Comments 0
You need to be logged in to leave comments. Login now