Skip to content

Commit f466307

Browse files
committed
Split up deprecated view notification to GitHub and other webhook endpoints
This sets a date for deprecated of these endpoints as Mar 1st 2019. Too soon? * Reduce complexity and drop decorator pattern for Notification classmethod pattern used in other notifications * Add notifications for non-GitHub incoming webhooks * Add docs as well
1 parent d745740 commit f466307

10 files changed

+134
-82
lines changed

docs/webhooks.rst

+31
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ details and a list of HTTP exchanges that have taken place for the integration.
2020
You need this information for the URL, webhook, or Payload URL needed by the
2121
repository provider such as GitHub, GitLab, or Bitbucket.
2222

23+
.. _webhook-creation:
24+
2325
Webhook Creation
2426
----------------
2527

@@ -181,3 +183,32 @@ on your Read the Docs project, or you can use a
181183
account.
182184

183185
.. [1] https://developer.github.com/changes/2018-04-25-github-services-deprecation/
186+
187+
.. _webhook-deprecated-endpoints:
188+
189+
I was warned that my project won't automatically build after Mar 1st
190+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
191+
192+
In addition to :ref:`no longer supporting GitHub Services <webhook-github-services>`,
193+
we have decided to no longer support several other legacy incoming webhook
194+
endpoints that were used before we introduced project webhook integrations. When
195+
we introduced our webhook integrations, we added several features and improved
196+
security for incoming webhooks and these features were not added to our leagcy
197+
incoming webhooks. New projects have not been able to use our legacy incoming
198+
webhooks since, however if you have a project that has been established for a
199+
while, you may still be using these endpoints.
200+
201+
After March 1st, 2019, we will stop accepting incoming webhook notifications for
202+
these legacy incoming webhooks. Your project will need to be reconfigured and
203+
have a webhook integration configured, pointing to a new webhook with your VCS
204+
provider.
205+
206+
In particular, the incoming webhook URLs that will be removed are:
207+
208+
* ``https://readthedocs.org/build``
209+
* ``https://readthedocs.org/bitbucket``
210+
* ``https://readthedocs.org/github`` (as noted :ref:`above <webhook-github-services>`)
211+
* ``https://readthedocs.org/gitlab``
212+
213+
In order to establish a new project webhook integration, :ref:`follow
214+
the directions for your VCS provider <webhook-creation>`

readthedocs/core/views/hooks.py

+20-4
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717

1818
from readthedocs.builds.constants import LATEST
1919
from readthedocs.core.utils import trigger_build
20-
from readthedocs.notifications.decorators import notify_deprecated_endpoint
2120
from readthedocs.projects import constants
2221
from readthedocs.projects.models import Feature, Project
22+
from readthedocs.projects.notifications import (
23+
DeprecatedBuildWebhookNotification,
24+
DeprecatedGitHubWebhookNotification,
25+
)
2326
from readthedocs.projects.tasks import sync_repository_task
2427

2528
log = logging.getLogger(__name__)
@@ -127,7 +130,6 @@ def log_info(project, msg):
127130
msg=msg))
128131

129132

130-
@notify_deprecated_endpoint
131133
def _build_url(url, projects, branches):
132134
"""
133135
Map a URL onto specific projects to build that are linked to that URL.
@@ -223,6 +225,9 @@ def github_build(request): # noqa: D205
223225
branches
224226
)
225227
projects = repo_projects | ssh_projects
228+
# TODO remove _build_url call and replace with a 4xx response after
229+
# Jan 31st.
230+
DeprecatedGitHubWebhookNotification.for_project_users(projects)
226231
return _build_url(http_search_url, projects, branches)
227232
except NoProjectException:
228233
log.exception('Project match not found: url=%s', http_search_url)
@@ -259,6 +264,9 @@ def gitlab_build(request): # noqa: D205
259264
)
260265
projects = get_project_from_url(search_url)
261266
if projects:
267+
# TODO remove _build_url call and replace with a 4xx response after
268+
# Mar 1st.
269+
DeprecatedBuildWebhookNotification.for_project_users(projects)
262270
return _build_url(search_url, projects, branches)
263271

264272
log.info('Project match not found: url=%s', search_url)
@@ -327,6 +335,9 @@ def bitbucket_build(request):
327335

328336
projects = get_project_from_url(search_url)
329337
if projects and branches:
338+
# TODO remove _build_url call and replace with a 4xx response after
339+
# Mar 1st.
340+
DeprecatedBuildWebhookNotification.for_project_users(projects)
330341
return _build_url(search_url, projects, branches)
331342

332343
if not branches:
@@ -343,7 +354,6 @@ def bitbucket_build(request):
343354

344355

345356
@csrf_exempt
346-
@notify_deprecated_endpoint
347357
def generic_build(request, project_id_or_slug=None):
348358
"""
349359
Generic webhook build endpoint.
@@ -373,7 +383,13 @@ def generic_build(request, project_id_or_slug=None):
373383
if request.method == 'POST':
374384
slug = request.POST.get('version_slug', project.default_version)
375385
log.info(
376-
"(Incoming Generic Build) %s [%s]", project.slug, slug)
386+
"(Incoming Generic Build) %s [%s]",
387+
project.slug,
388+
slug,
389+
)
390+
# TODO remove _build_url call and replace with a 4xx response after
391+
# Mar 1st.
392+
DeprecatedBuildWebhookNotification.for_project_users([project])
377393
_build_version(project, slug)
378394
else:
379395
return HttpResponse("You must POST to this resource.")

readthedocs/notifications/decorators.py

-51
This file was deleted.

readthedocs/projects/notifications.py

+66-17
Original file line numberDiff line numberDiff line change
@@ -17,44 +17,83 @@ class ResourceUsageNotification(Notification):
1717
level = REQUIREMENT
1818

1919

20-
class DeprecatedWebhookEndpointNotification(Notification):
20+
class DeprecatedViewNotification(Notification):
2121

2222
"""
23-
Notification for the usage of deprecated webhook endpoints.
24-
25-
Each time that a view decorated with ``notify_deprecated_endpoint`` is hit,
26-
a new instance of this class is created. Then, ``.send`` is called and the
27-
``SiteBackend`` will create (avoiding duplication) a site notification and
28-
the ``EmailBackend`` will do nothing (because of ``send_email=False``).
29-
30-
Besides, a ``message_extends.models.Message`` object is created to track
31-
sending an email if this endpoint is hit again after ``email_period``. When,
32-
``.send`` is call and the ``email_period`` was reach from the
33-
``Message.created`` time we mark ``send_email=True`` in this instance and
34-
call the super ``.send`` method that will effectively send the email and
35-
mark the message as ``extra_tags='email_sent'``.
23+
Notification to alert user of a view that is going away.
24+
25+
This notification is used for cases where we want to alert the project
26+
users that a view that they are using is going to be going away.
27+
28+
.. warning::
29+
This is currently used primarily for deprecated webhook endpoints, which
30+
aren't even hit by a user. There are likely some mechanics to this
31+
class that expect a webhook endpoint and not a generic view.
32+
33+
The first time that a notification is sent to a user, ``SiteBackend`` will
34+
create (avoiding duplication) a site notification and the ``EmailBackend``
35+
will send an email notification. The :py:cls:`Message` object will now have
36+
``extra_tags`` of ``email_delayed``. This means that the first email was
37+
sent and that we won't send the second email for
38+
``DeprecatedViewNotification.email_period``.
39+
40+
The second time that a notification is sent to a user,
41+
:py:cls:`message_extends.models.Message` will deduplicate the site message
42+
and we rely on message meta data, stored in ``extra_tags`` on the model, to
43+
determine if an email was already sent. We will send a second email to a
44+
:py:cls:`Message` that has ``email_delayed`` in extra_tags, at which point
45+
we'll set ``extra_tags`` to have ``email_sent``. Any further attempts to
46+
send a message won't work as the ``extra_tags`` is no longer
47+
``email_delayed``.
3648
"""
3749

38-
name = 'deprecated_webhook_endpoint'
50+
# This is an abstract class, we won't set a name yet.
51+
name = None
3952
context_object_name = 'project'
4053
subject = '{{ project.name }} project webhook needs to be updated'
4154
send_email = False
4255
email_period = timedelta(days=7)
4356
level = REQUIREMENT
4457

4558
def __init__(self, context_object, request, user=None):
46-
super(DeprecatedWebhookEndpointNotification, self).__init__(
59+
super(DeprecatedViewNotification, self).__init__(
4760
context_object,
4861
request,
4962
user,
5063
)
5164
self.message, created = self._create_message()
5265

66+
if self.name is None:
67+
raise ValueError('{} is an abstract class.'.format(
68+
self.__class__.__name__,
69+
))
70+
5371
# Mark this notification to be sent as email the first time that it's
5472
# created (user hits this endpoint for the first time)
5573
if created:
5674
self.send_email = True
5775

76+
@classmethod
77+
def for_project_users(cls, projects):
78+
"""
79+
Notify project users of deprecated view.
80+
81+
This is primarily used for deprecated webhook endpoints, though is not
82+
particular to this usage.
83+
84+
:param projects: List of project instances
85+
:type projects: [:py:class:`Project`]
86+
"""
87+
for project in projects:
88+
# Send one notification to each owner of the project
89+
for user in project.users.all():
90+
notification = cls(
91+
context_object=project,
92+
request=HttpRequest(),
93+
user=user,
94+
)
95+
notification.send()
96+
5897
def _create_message(self):
5998
# Each time this class is instantiated we create a new Message (it's
6099
# de-duplicated by using the ``message``, ``user`` and ``extra_tags``
@@ -80,4 +119,14 @@ def send(self, *args, **kwargs): # noqa
80119
# de-duplicate the following one
81120
self._create_message()
82121

83-
super(DeprecatedWebhookEndpointNotification, self).send(*args, **kwargs)
122+
super(DeprecatedViewNotification, self).send(*args, **kwargs)
123+
124+
125+
class DeprecatedGitHubWebhookNotification(DeprecatedViewNotification):
126+
127+
name = 'deprecated_github_webhook'
128+
129+
130+
class DeprecatedBuildWebhookNotification(DeprecatedViewNotification):
131+
132+
name = 'deprecated_build_webhook'

readthedocs/rtd_tests/tests/test_notifications.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from readthedocs.notifications.backends import EmailBackend, SiteBackend
1818
from readthedocs.notifications.constants import ERROR, INFO_NON_PERSISTENT, WARNING_NON_PERSISTENT
1919
from readthedocs.projects.models import Project
20-
from readthedocs.projects.notifications import DeprecatedWebhookEndpointNotification
20+
from readthedocs.projects.notifications import DeprecatedViewNotification
2121
from readthedocs.builds.models import Build
2222

2323

@@ -239,7 +239,7 @@ def setUp(self):
239239
self.user = fixture.get(User)
240240
self.request = HttpRequest()
241241

242-
self.notification = DeprecatedWebhookEndpointNotification(
242+
self.notification = DeprecatedViewNotification(
243243
self.project,
244244
self.request,
245245
self.user,
@@ -248,21 +248,21 @@ def setUp(self):
248248
def test_deduplication(self):
249249
self.assertEqual(PersistentMessage.objects.filter(user=self.user).count(), 1)
250250
for x in range(5):
251-
DeprecatedWebhookEndpointNotification(
251+
DeprecatedViewNotification(
252252
self.project,
253253
self.request,
254254
self.user,
255255
)
256256
self.assertEqual(PersistentMessage.objects.filter(user=self.user).count(), 1)
257-
DeprecatedWebhookEndpointNotification(
257+
DeprecatedViewNotification(
258258
self.project,
259259
self.request,
260260
fixture.get(User),
261261
)
262262
self.assertEqual(PersistentMessage.objects.count(), 2)
263263
self.notification.message.extra_tags = 'email_sent'
264264
self.notification.message.save()
265-
DeprecatedWebhookEndpointNotification(
265+
DeprecatedViewNotification(
266266
self.project,
267267
self.request,
268268
self.user,
@@ -285,7 +285,7 @@ def test_send_email(self, send_email):
285285

286286
# Hit the endpoint twice
287287
for x in range(2):
288-
notification = DeprecatedWebhookEndpointNotification(
288+
notification = DeprecatedViewNotification(
289289
self.project,
290290
self.request,
291291
self.user,
@@ -294,7 +294,7 @@ def test_send_email(self, send_email):
294294
# the message also to be sure that no new notification was created
295295
self.assertEqual(PersistentMessage.objects.filter(
296296
user=self.user,
297-
message__startswith=DeprecatedWebhookEndpointNotification.name).count(),
297+
message__startswith=DeprecatedViewNotification.name).count(),
298298
1,
299299
)
300300
self.assertFalse(notification.send_email) # second notification
@@ -313,7 +313,7 @@ def test_send_email(self, send_email):
313313
# A new Message object is created
314314
self.assertEqual(PersistentMessage.objects.filter(
315315
user=self.user,
316-
message__startswith=DeprecatedWebhookEndpointNotification.name).count(),
316+
message__startswith=DeprecatedViewNotification.name).count(),
317317
2,
318318
)
319319
self.assertEqual(PersistentMessage.objects.last().extra_tags, 'email_delayed')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<p>Your project, {{ project.name }}, is currently using a legacy incoming webhook to trigger builds on Read the Docs. Effective March 1st, 2019, Read the Docs will no longer accept incoming webhooks through these endpoints.</p>
2+
3+
<p>To continue building your Read the Docs project on changes to your repository, you will need to configure a new webhook with your VCS provider. You can find more information on how to configure a new webhook in our documentation, at:</p>
4+
5+
{% comment %}Plain text link because of text version of email{% endcomment %}
6+
<p><a href="https://docs.readthedocs.io/en/latest/webhooks.html#webhook-deprecated-endpoints">https://docs.readthedocs.io/en/latest/webhooks.html#webhook-deprecated-endpoints</a></p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after March 1st, 2019. For more information, <a href="https://docs.readthedocs.io/en/latest/webhooks.html#webhook-deprecated-endpoints">see our documentation on webhook integrations</a>.

readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_email.html renamed to readthedocs/templates/projects/notifications/deprecated_github_webhook_email.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
<p>You can find more information on our webhook intergrations in our documentation, at:</p>
66

77
{% comment %}Plain text link because of text version of email{% endcomment %}
8-
<p><a href="https://docs.readthedocs.io/en/latest/webhooks.html">https://docs.readthedocs.io/en/latest/webhooks.html</a></p>
8+
<p><a href="https://docs.readthedocs.io/en/latest/webhooks.html#webhook-github-services">https://docs.readthedocs.io/en/latest/webhooks.html#webhook-github-services</a></p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after Jan 31st, 2019. For more information, <a href="https://docs.readthedocs.io/en/latest/webhooks.html#webhook-github-services">see our documentation on webhook integrations</a>.

readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_site.html

-1
This file was deleted.

0 commit comments

Comments
 (0)