|
1 | 1 | """Views pertaining to builds."""
|
2 | 2 |
|
3 |
| -import json |
4 | 3 | import logging
|
5 |
| -import re |
6 | 4 |
|
7 |
| -from django.http import HttpResponse, HttpResponseNotFound |
8 |
| -from django.shortcuts import redirect |
9 |
| -from django.views.decorators.csrf import csrf_exempt |
10 |
| - |
11 |
| -from readthedocs.builds.constants import LATEST |
12 | 5 | from readthedocs.core.utils import trigger_build
|
13 |
| -from readthedocs.projects import constants |
14 |
| -from readthedocs.projects.models import Feature, Project |
15 | 6 | from readthedocs.projects.tasks import sync_repository_task
|
16 | 7 |
|
17 | 8 |
|
18 | 9 | log = logging.getLogger(__name__)
|
19 | 10 |
|
20 | 11 |
|
21 |
| -class NoProjectException(Exception): |
22 |
| - pass |
23 |
| - |
24 |
| - |
25 |
| -def _allow_deprecated_webhook(project): |
26 |
| - return project.has_feature(Feature.ALLOW_DEPRECATED_WEBHOOKS) |
27 |
| - |
28 |
| - |
29 | 12 | def _build_version(project, slug, already_built=()):
|
30 | 13 | """
|
31 | 14 | Where we actually trigger builds for a project and slug.
|
@@ -105,284 +88,3 @@ def sync_versions(project):
|
105 | 88 | except Exception:
|
106 | 89 | log.exception('Unknown sync versions exception')
|
107 | 90 | return None
|
108 |
| - |
109 |
| - |
110 |
| -def get_project_from_url(url): |
111 |
| - if not url: |
112 |
| - return Project.objects.none() |
113 |
| - projects = ( |
114 |
| - Project.objects.filter(repo__iendswith=url) | |
115 |
| - Project.objects.filter(repo__iendswith=url + '.git') |
116 |
| - ) |
117 |
| - return projects |
118 |
| - |
119 |
| - |
120 |
| -def log_info(project, msg): |
121 |
| - log.info( |
122 |
| - constants.LOG_TEMPLATE, |
123 |
| - { |
124 |
| - 'project': project, |
125 |
| - 'version': '', |
126 |
| - 'msg': msg, |
127 |
| - } |
128 |
| - ) |
129 |
| - |
130 |
| - |
131 |
| -def _build_url(url, projects, branches): |
132 |
| - """ |
133 |
| - Map a URL onto specific projects to build that are linked to that URL. |
134 |
| -
|
135 |
| - Check each of the ``branches`` to see if they are active and should be |
136 |
| - built. |
137 |
| - """ |
138 |
| - ret = '' |
139 |
| - all_built = {} |
140 |
| - all_not_building = {} |
141 |
| - |
142 |
| - # This endpoint doesn't require authorization, we shouldn't allow builds to |
143 |
| - # be triggered from this any longer. Deprecation plan is to selectively |
144 |
| - # allow access to this endpoint for now. |
145 |
| - if not any(_allow_deprecated_webhook(project) for project in projects): |
146 |
| - return HttpResponse('This API endpoint is deprecated', status=403) |
147 |
| - |
148 |
| - for project in projects: |
149 |
| - (built, not_building) = build_branches(project, branches) |
150 |
| - if not built: |
151 |
| - # Call sync_repository_task to update tag/branch info |
152 |
| - version = project.versions.get(slug=LATEST) |
153 |
| - sync_repository_task.delay(version.pk) |
154 |
| - msg = '(URL Build) Syncing versions for %s' % project.slug |
155 |
| - log.info(msg) |
156 |
| - all_built[project.slug] = built |
157 |
| - all_not_building[project.slug] = not_building |
158 |
| - |
159 |
| - for project_slug, built in list(all_built.items()): |
160 |
| - if built: |
161 |
| - msg = '(URL Build) Build Started: {} [{}]'.format( |
162 |
| - url, |
163 |
| - ' '.join(built), |
164 |
| - ) |
165 |
| - log_info(project_slug, msg=msg) |
166 |
| - ret += msg |
167 |
| - |
168 |
| - for project_slug, not_building in list(all_not_building.items()): |
169 |
| - if not_building: |
170 |
| - msg = '(URL Build) Not Building: {} [{}]'.format( |
171 |
| - url, |
172 |
| - ' '.join(not_building), |
173 |
| - ) |
174 |
| - log_info(project_slug, msg=msg) |
175 |
| - ret += msg |
176 |
| - |
177 |
| - if not ret: |
178 |
| - ret = '(URL Build) No known branches were pushed to.' |
179 |
| - |
180 |
| - return HttpResponse(ret) |
181 |
| - |
182 |
| - |
183 |
| -@csrf_exempt |
184 |
| -def github_build(request): # noqa: D205 |
185 |
| - """ |
186 |
| - GitHub webhook consumer. |
187 |
| -
|
188 |
| - .. warning:: **DEPRECATED** |
189 |
| - Use :py:class:`readthedocs.api.v2.views.integrations.GitHubWebhookView` |
190 |
| - instead of this view function |
191 |
| -
|
192 |
| - This will search for projects matching either a stripped down HTTP or SSH |
193 |
| - URL. The search is error prone, use the API v2 webhook for new webhooks. |
194 |
| -
|
195 |
| - Old webhooks may not have specified the content type to POST with, and |
196 |
| - therefore can use ``application/x-www-form-urlencoded`` to pass the JSON |
197 |
| - payload. More information on the API docs here: |
198 |
| - https://developer.github.com/webhooks/creating/#content-type |
199 |
| - """ |
200 |
| - if request.method == 'POST': |
201 |
| - try: |
202 |
| - if request.META['CONTENT_TYPE'] == 'application/x-www-form-urlencoded': |
203 |
| - data = json.loads(request.POST.get('payload')) |
204 |
| - else: |
205 |
| - data = json.loads(request.body) |
206 |
| - http_url = data['repository']['url'] |
207 |
| - http_search_url = http_url.replace('http://', '').replace('https://', '') |
208 |
| - ssh_url = data['repository']['ssh_url'] |
209 |
| - ssh_search_url = ssh_url.replace('git@', '').replace('.git', '') |
210 |
| - branches = [data['ref'].replace('refs/heads/', '')] |
211 |
| - except (ValueError, TypeError, KeyError): |
212 |
| - log.exception('Invalid GitHub webhook payload') |
213 |
| - return HttpResponse('Invalid request', status=400) |
214 |
| - try: |
215 |
| - repo_projects = get_project_from_url(http_search_url) |
216 |
| - if repo_projects: |
217 |
| - log.info( |
218 |
| - 'GitHub webhook search: url=%s branches=%s', |
219 |
| - http_search_url, |
220 |
| - branches, |
221 |
| - ) |
222 |
| - ssh_projects = get_project_from_url(ssh_search_url) |
223 |
| - if ssh_projects: |
224 |
| - log.info( |
225 |
| - 'GitHub webhook search: url=%s branches=%s', |
226 |
| - ssh_search_url, |
227 |
| - branches, |
228 |
| - ) |
229 |
| - projects = repo_projects | ssh_projects |
230 |
| - return _build_url(http_search_url, projects, branches) |
231 |
| - except NoProjectException: |
232 |
| - log.exception('Project match not found: url=%s', http_search_url) |
233 |
| - return HttpResponseNotFound('Project not found') |
234 |
| - else: |
235 |
| - return HttpResponse('Method not allowed, POST is required', status=405) |
236 |
| - |
237 |
| - |
238 |
| -@csrf_exempt |
239 |
| -def gitlab_build(request): # noqa: D205 |
240 |
| - """ |
241 |
| - GitLab webhook consumer. |
242 |
| -
|
243 |
| - .. warning:: **DEPRECATED** |
244 |
| - Use :py:class:`readthedocs.api.v2.views.integrations.GitLabWebhookView` |
245 |
| - instead of this view function |
246 |
| -
|
247 |
| - Search project repository URLs using the site URL from GitLab webhook payload. |
248 |
| - This search is error-prone, use the API v2 webhook view for new webhooks. |
249 |
| - """ |
250 |
| - if request.method == 'POST': |
251 |
| - try: |
252 |
| - data = json.loads(request.body) |
253 |
| - url = data['project']['http_url'] |
254 |
| - search_url = re.sub(r'^https?://(.*?)(?:\.git|)$', '\\1', url) |
255 |
| - branches = [data['ref'].replace('refs/heads/', '')] |
256 |
| - except (ValueError, TypeError, KeyError): |
257 |
| - log.exception('Invalid GitLab webhook payload') |
258 |
| - return HttpResponse('Invalid request', status=400) |
259 |
| - log.info( |
260 |
| - 'GitLab webhook search: url=%s branches=%s', |
261 |
| - search_url, |
262 |
| - branches, |
263 |
| - ) |
264 |
| - projects = get_project_from_url(search_url) |
265 |
| - if projects: |
266 |
| - return _build_url(search_url, projects, branches) |
267 |
| - |
268 |
| - log.info('Project match not found: url=%s', search_url) |
269 |
| - return HttpResponseNotFound('Project match not found') |
270 |
| - return HttpResponse('Method not allowed, POST is required', status=405) |
271 |
| - |
272 |
| - |
273 |
| -@csrf_exempt |
274 |
| -def bitbucket_build(request): |
275 |
| - """ |
276 |
| - Consume webhooks from multiple versions of Bitbucket's API. |
277 |
| -
|
278 |
| - .. warning:: **DEPRECATED** |
279 |
| - Use :py:class:`readthedocs.api.v2.views.integrations.BitbucketWebhookView` |
280 |
| - instead of this view function |
281 |
| -
|
282 |
| - New webhooks are set up with v2, but v1 webhooks will still point to this |
283 |
| - endpoint. There are also "services" that point here and submit |
284 |
| - ``application/x-www-form-urlencoded`` data. |
285 |
| -
|
286 |
| - API v1 |
287 |
| - https://confluence.atlassian.com/bitbucket/events-resources-296095220.html |
288 |
| -
|
289 |
| - API v2 |
290 |
| - https://confluence.atlassian.com/bitbucket/event-payloads-740262817.html#EventPayloads-Push |
291 |
| -
|
292 |
| - Services |
293 |
| - https://confluence.atlassian.com/bitbucket/post-service-management-223216518.html |
294 |
| - """ |
295 |
| - if request.method == 'POST': |
296 |
| - try: |
297 |
| - if request.META['CONTENT_TYPE'] == 'application/x-www-form-urlencoded': |
298 |
| - data = json.loads(request.POST.get('payload')) |
299 |
| - else: |
300 |
| - data = json.loads(request.body) |
301 |
| - |
302 |
| - version = 2 if request.META.get('HTTP_USER_AGENT') == 'Bitbucket-Webhooks/2.0' else 1 # yapf: disabled # noqa |
303 |
| - if version == 1: |
304 |
| - branches = [ |
305 |
| - commit.get('branch', '') for commit in data['commits'] |
306 |
| - ] |
307 |
| - repository = data['repository'] |
308 |
| - if not repository['absolute_url']: |
309 |
| - return HttpResponse('Invalid request', status=400) |
310 |
| - search_url = 'bitbucket.org{}'.format( |
311 |
| - repository['absolute_url'].rstrip('/'), |
312 |
| - ) |
313 |
| - elif version == 2: |
314 |
| - changes = data['push']['changes'] |
315 |
| - branches = [change['new']['name'] for change in changes] |
316 |
| - if not data['repository']['full_name']: |
317 |
| - return HttpResponse('Invalid request', status=400) |
318 |
| - search_url = 'bitbucket.org/{}'.format( |
319 |
| - data['repository']['full_name'], |
320 |
| - ) |
321 |
| - except (TypeError, ValueError, KeyError): |
322 |
| - log.exception('Invalid Bitbucket webhook payload') |
323 |
| - return HttpResponse('Invalid request', status=400) |
324 |
| - |
325 |
| - log.info( |
326 |
| - 'Bitbucket webhook search: url=%s branches=%s', |
327 |
| - search_url, |
328 |
| - branches, |
329 |
| - ) |
330 |
| - log.debug('Bitbucket webhook payload:\n\n%s\n\n', data) |
331 |
| - |
332 |
| - projects = get_project_from_url(search_url) |
333 |
| - if projects and branches: |
334 |
| - return _build_url(search_url, projects, branches) |
335 |
| - |
336 |
| - if not branches: |
337 |
| - log.info( |
338 |
| - 'Commit/branch not found url=%s branches=%s', |
339 |
| - search_url, |
340 |
| - branches, |
341 |
| - ) |
342 |
| - return HttpResponseNotFound('Commit/branch not found') |
343 |
| - |
344 |
| - log.info('Project match not found: url=%s', search_url) |
345 |
| - return HttpResponseNotFound('Project match not found') |
346 |
| - return HttpResponse('Method not allowed, POST is required', status=405) |
347 |
| - |
348 |
| - |
349 |
| -@csrf_exempt |
350 |
| -def generic_build(request, project_id_or_slug=None): |
351 |
| - """ |
352 |
| - Generic webhook build endpoint. |
353 |
| -
|
354 |
| - .. warning:: **DEPRECATED** |
355 |
| -
|
356 |
| - Use :py:class:`readthedocs.api.v2.views.integrations.GenericWebhookView` |
357 |
| - instead of this view function |
358 |
| - """ |
359 |
| - try: |
360 |
| - project = Project.objects.get(pk=project_id_or_slug) |
361 |
| - # Allow slugs too |
362 |
| - except (Project.DoesNotExist, ValueError): |
363 |
| - try: |
364 |
| - project = Project.objects.get(slug=project_id_or_slug) |
365 |
| - except (Project.DoesNotExist, ValueError): |
366 |
| - log.exception( |
367 |
| - '(Incoming Generic Build) Repo not found: %s', |
368 |
| - project_id_or_slug, |
369 |
| - ) |
370 |
| - return HttpResponseNotFound( |
371 |
| - 'Repo not found: %s' % project_id_or_slug, |
372 |
| - ) |
373 |
| - # This endpoint doesn't require authorization, we shouldn't allow builds to |
374 |
| - # be triggered from this any longer. Deprecation plan is to selectively |
375 |
| - # allow access to this endpoint for now. |
376 |
| - if not _allow_deprecated_webhook(project): |
377 |
| - return HttpResponse('This API endpoint is deprecated', status=403) |
378 |
| - if request.method == 'POST': |
379 |
| - slug = request.POST.get('version_slug', project.default_version) |
380 |
| - log.info( |
381 |
| - '(Incoming Generic Build) %s [%s]', |
382 |
| - project.slug, |
383 |
| - slug, |
384 |
| - ) |
385 |
| - _build_version(project, slug) |
386 |
| - else: |
387 |
| - return HttpResponse('You must POST to this resource.') |
388 |
| - return redirect('builds_project_list', project.slug) |
0 commit comments