Skip to content

Split up deprecated view notification to GitHub and other webhook endpoints #5083

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
31 changes: 31 additions & 0 deletions docs/webhooks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ details and a list of HTTP exchanges that have taken place for the integration.
You need this information for the URL, webhook, or Payload URL needed by the
repository provider such as GitHub, GitLab, or Bitbucket.

.. _webhook-creation:

Webhook Creation
----------------

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

.. [1] https://developer.github.com/changes/2018-04-25-github-services-deprecation/

.. _webhook-deprecated-endpoints:

I was warned that my project won't automatically build after April 1st
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In addition to :ref:`no longer supporting GitHub Services <webhook-github-services>`,
we have decided to no longer support several other legacy incoming webhook
endpoints that were used before we introduced project webhook integrations. When
we introduced our webhook integrations, we added several features and improved
security for incoming webhooks and these features were not added to our leagcy
incoming webhooks. New projects have not been able to use our legacy incoming
webhooks since, however if you have a project that has been established for a
while, you may still be using these endpoints.

After March 1st, 2019, we will stop accepting incoming webhook notifications for
these legacy incoming webhooks. Your project will need to be reconfigured and
have a webhook integration configured, pointing to a new webhook with your VCS
provider.

In particular, the incoming webhook URLs that will be removed are:

* ``https://readthedocs.org/build``
* ``https://readthedocs.org/bitbucket``
* ``https://readthedocs.org/github`` (as noted :ref:`above <webhook-github-services>`)
* ``https://readthedocs.org/gitlab``

In order to establish a new project webhook integration, :ref:`follow
the directions for your VCS provider <webhook-creation>`
8 changes: 4 additions & 4 deletions readthedocs/core/views/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

from readthedocs.builds.constants import LATEST
from readthedocs.core.utils import trigger_build
from readthedocs.notifications.decorators import notify_deprecated_endpoint
from readthedocs.projects import constants
from readthedocs.projects.models import Feature, Project
from readthedocs.projects.tasks import sync_repository_task
Expand Down Expand Up @@ -127,7 +126,6 @@ def log_info(project, msg):
msg=msg))


@notify_deprecated_endpoint
def _build_url(url, projects, branches):
"""
Map a URL onto specific projects to build that are linked to that URL.
Expand Down Expand Up @@ -343,7 +341,6 @@ def bitbucket_build(request):


@csrf_exempt
@notify_deprecated_endpoint
def generic_build(request, project_id_or_slug=None):
"""
Generic webhook build endpoint.
Expand Down Expand Up @@ -373,7 +370,10 @@ def generic_build(request, project_id_or_slug=None):
if request.method == 'POST':
slug = request.POST.get('version_slug', project.default_version)
log.info(
"(Incoming Generic Build) %s [%s]", project.slug, slug)
"(Incoming Generic Build) %s [%s]",
project.slug,
slug,
)
_build_version(project, slug)
else:
return HttpResponse("You must POST to this resource.")
Expand Down
13 changes: 7 additions & 6 deletions readthedocs/notifications/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,7 @@ def send_notification(request, notification):
backends = getattr(settings, 'NOTIFICATION_BACKENDS', [])
for cls_name in backends:
backend = import_string(cls_name)(request)
# Do not send email notification if defined explicitly
if backend.name == EmailBackend.name and not notification.send_email:
pass
else:
backend.send(notification)
backend.send(notification)


class Backend(object):
Expand All @@ -55,11 +51,16 @@ class EmailBackend(Backend):

The content body is first rendered from an on-disk template, then passed
into the standard email templates as a string.

If the notification is set to ``send_email=False``, this backend will exit
early from :py:meth:`send`.
"""

name = 'email'

def send(self, notification):
if not notification.send_email:
return
# FIXME: if the level is an ERROR an email is received and sometimes
# it's not necessary. This behavior should be clearly documented in the
# code
Expand Down Expand Up @@ -114,6 +115,6 @@ def send(self, notification):
backend_name=self.name,
source_format=HTML,
),
extra_tags='',
extra_tags=notification.extra_tags,
user=notification.user,
)
51 changes: 0 additions & 51 deletions readthedocs/notifications/decorators.py

This file was deleted.

1 change: 1 addition & 0 deletions readthedocs/notifications/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class Notification(object):
subject = None
user = None
send_email = True
extra_tags = ''

def __init__(self, context_object, request, user=None):
self.object = context_object
Expand Down
14 changes: 11 additions & 3 deletions readthedocs/projects/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,20 @@
ProjectRelationship,
WebHook,
)
from .notifications import ResourceUsageNotification
from .notifications import (
ResourceUsageNotification,
DeprecatedBuildWebhookNotification,
DeprecatedGitHubWebhookNotification,
)
from .tasks import remove_dir


class ProjectSendNotificationView(SendNotificationView):
notification_classes = [ResourceUsageNotification]
notification_classes = [
ResourceUsageNotification,
DeprecatedBuildWebhookNotification,
DeprecatedGitHubWebhookNotification,
]

def get_object_recipients(self, obj):
for owner in obj.users.all():
Expand Down Expand Up @@ -119,7 +127,7 @@ class ProjectAdmin(GuardedModelAdmin):
list_display = ('name', 'slug', 'repo', 'repo_type', 'featured')
list_filter = ('repo_type', 'featured', 'privacy_level',
'documentation_type', 'programming_language',
ProjectOwnerBannedFilter)
'feature__feature_id', ProjectOwnerBannedFilter)
list_editable = ('featured',)
search_fields = ('slug', 'repo')
inlines = [ProjectRelationshipInline, RedirectInline,
Expand Down
88 changes: 30 additions & 58 deletions readthedocs/projects/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from __future__ import absolute_import
from datetime import timedelta
from django.utils import timezone
from django.http import HttpRequest
from messages_extends.models import Message
from readthedocs.notifications import Notification
from readthedocs.notifications.constants import REQUIREMENT
Expand All @@ -17,67 +18,38 @@ class ResourceUsageNotification(Notification):
level = REQUIREMENT


class DeprecatedWebhookEndpointNotification(Notification):
class DeprecatedViewNotification(Notification):

"""
Notification for the usage of deprecated webhook endpoints.
"""Notification to alert user of a view that is going away."""

Each time that a view decorated with ``notify_deprecated_endpoint`` is hit,
a new instance of this class is created. Then, ``.send`` is called and the
``SiteBackend`` will create (avoiding duplication) a site notification and
the ``EmailBackend`` will do nothing (because of ``send_email=False``).

Besides, a ``message_extends.models.Message`` object is created to track
sending an email if this endpoint is hit again after ``email_period``. When,
``.send`` is call and the ``email_period`` was reach from the
``Message.created`` time we mark ``send_email=True`` in this instance and
call the super ``.send`` method that will effectively send the email and
mark the message as ``extra_tags='email_sent'``.
"""

name = 'deprecated_webhook_endpoint'
context_object_name = 'project'
subject = '{{ project.name }} project webhook needs to be updated'
send_email = False
email_period = timedelta(days=7)
level = REQUIREMENT

def __init__(self, context_object, request, user=None):
super(DeprecatedWebhookEndpointNotification, self).__init__(
context_object,
request,
user,
)
self.message, created = self._create_message()

# Mark this notification to be sent as email the first time that it's
# created (user hits this endpoint for the first time)
if created:
self.send_email = True

def _create_message(self):
# Each time this class is instantiated we create a new Message (it's
# de-duplicated by using the ``message``, ``user`` and ``extra_tags``
# status)
return Message.objects.get_or_create(
message='{}: {}'.format(self.name, self.get_subject()),
level=self.level,
user=self.user,
extra_tags='email_delayed',
)

def send(self, *args, **kwargs): # noqa
if self.message.created + self.email_period < timezone.now():
# Mark this instance to really send the email and rely on the
# EmailBackend to effectively send the email
self.send_email = True

# Mark the message as sent and send the email
self.message.extra_tags = 'email_sent'
self.message.save()

# Create a new Message with ``email_delayed`` so we are prepared to
# de-duplicate the following one
self._create_message()

super(DeprecatedWebhookEndpointNotification, self).send(*args, **kwargs)
@classmethod
def notify_project_users(cls, projects):
"""
Notify project users of deprecated view.

:param projects: List of project instances
:type projects: [:py:class:`Project`]
"""
for project in projects:
# Send one notification to each owner of the project
for user in project.users.all():
notification = cls(
context_object=project,
request=HttpRequest(),
user=user,
)
notification.send()


class DeprecatedGitHubWebhookNotification(DeprecatedViewNotification):

name = 'deprecated_github_webhook'


class DeprecatedBuildWebhookNotification(DeprecatedViewNotification):

name = 'deprecated_build_webhook'
Loading