Skip to content

Notify users about the usage of deprecated webhooks #4898

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
merged 9 commits into from
Jan 11, 2019
76 changes: 76 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 All @@ -36,6 +38,8 @@ As an example, the URL pattern looks like this: *readthedocs.org/api/v2/webhook/

Use this URL when setting up a new webhook with your provider -- these steps vary depending on the provider:

.. _webhook-integration-github:

GitHub
~~~~~~

Expand All @@ -53,6 +57,8 @@ For a 403 error, it's likely that the Payload URL is incorrect.

.. note:: The webhook token, intended for the GitHub **Secret** field, is not yet implemented.

.. _webhook-integration-bitbucket:

Bitbucket
~~~~~~~~~

Expand All @@ -63,6 +69,8 @@ Bitbucket
* Under **Triggers**, **Repository push** should be selected
* Finish by clicking **Save**

.. _webhook-integration-gitlab:

GitLab
~~~~~~

Expand All @@ -73,6 +81,8 @@ GitLab
* Leave the default **Push events** selected and mark **Tag push events** also
* Finish by clicking **Add Webhook**

.. _webhook-integration-generic:

Using the generic API integration
---------------------------------

Expand Down Expand Up @@ -136,3 +146,69 @@ Resyncing webhooks
It might be necessary to re-establish a webhook if you are noticing problems.
To resync a webhook from Read the Docs, visit the integration detail page and
follow the directions for re-syncing your repository webhook.

Troubleshooting
---------------

My project isn't automatically building
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If your project isn't automatically building, you can check your integration on
Read the Docs to see the payload sent to our servers. If there is no recent
activity on your Read the Docs project webhook integration, then it's likely
that your VCS provider is not configured correctly. If there is payload
information on your Read the Docs project, you might need to verify that your
versions are configured to build correctly.

Either way, it may help to either resync your webhook intergration (see
`Resyncing webhooks`_ for information on this process), or set up an entirely
new webhook intergration.

.. _webhook-github-services:

I was warned I shouldn't use GitHub Services
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Last year, GitHub announced that effective Jan 31st, 2019, GitHub Services will stop
working [1]_. This means GitHub will stop sending notifications to Read the Docs
for projects configured with the ``ReadTheDocs`` GitHub Service. If your project
has been configured on Read the Docs for a long time, you are most likely still
using this service to automatically build your project on Read the Docs.

In order for your project to continue automatically building, you will need to
configure your GitHub repository with a new webhook. You can use either a
connected GitHub account and a :ref:`GitHub webhook integration <webhook-integration-github>`
on your Read the Docs project, or you can use a
:ref:`generic webhook integraiton <webhook-integration-generic>` without a connected
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>`
5 changes: 4 additions & 1 deletion readthedocs/core/views/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,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,
)
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
42 changes: 42 additions & 0 deletions readthedocs/projects/notifications.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# -*- coding: utf-8 -*-
"""Project notifications"""

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 @@ -11,3 +16,40 @@ class ResourceUsageNotification(Notification):
context_object_name = 'project'
subject = 'Builds for {{ project.name }} are using too many resources'
level = REQUIREMENT


class DeprecatedViewNotification(Notification):

"""Notification to alert user of a view that is going away."""

context_object_name = 'project'
subject = '{{ project.name }} project webhook needs to be updated'
level = REQUIREMENT

@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'
48 changes: 48 additions & 0 deletions readthedocs/rtd_tests/tests/test_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@
"""Notification tests"""

from __future__ import absolute_import
from datetime import timedelta
import mock
import django_dynamic_fixture as fixture
from django.http import HttpRequest
from django.test import TestCase
from django.test.utils import override_settings
from django.contrib.auth.models import User, AnonymousUser
from django.utils import timezone
from messages_extends.models import Message as PersistentMessage

from readthedocs.notifications import Notification, SiteNotification
from readthedocs.notifications.backends import EmailBackend, SiteBackend
from readthedocs.notifications.constants import ERROR, INFO_NON_PERSISTENT, WARNING_NON_PERSISTENT
from readthedocs.projects.models import Project
from readthedocs.projects.notifications import (
DeprecatedGitHubWebhookNotification,
DeprecatedBuildWebhookNotification,
)
from readthedocs.builds.models import Build


Expand Down Expand Up @@ -222,3 +230,43 @@ def test_message(self):
with mock.patch('readthedocs.notifications.notification.log') as mock_log:
self.assertEqual(self.n.get_message(False), '')
mock_log.error.assert_called_once()


class DeprecatedWebhookEndpointNotificationTests(TestCase):

def setUp(self):
PersistentMessage.objects.all().delete()

self.user = fixture.get(User)
self.project = fixture.get(Project, users=[self.user])
self.request = HttpRequest()

self.notification = DeprecatedBuildWebhookNotification(
self.project,
self.request,
self.user,
)

@mock.patch('readthedocs.notifications.backends.send_email')
def test_dedupliation(self, send_email):
user = fixture.get(User)
project = fixture.get(Project, main_language_project=None)
project.users.add(user)
project.refresh_from_db()
self.assertEqual(project.users.count(), 1)

self.assertEqual(PersistentMessage.objects.filter(user=user).count(), 0)
DeprecatedGitHubWebhookNotification.notify_project_users([project])

# Site and email notification will go out, site message doesn't have
# any reason to deduplicate yet
self.assertEqual(PersistentMessage.objects.filter(user=user).count(), 1)
self.assertTrue(send_email.called)
send_email.reset_mock()
self.assertFalse(send_email.called)

# Expect the site message to deduplicate, the email won't
DeprecatedGitHubWebhookNotification.notify_project_users([project])
self.assertEqual(PersistentMessage.objects.filter(user=user).count(), 1)
self.assertTrue(send_email.called)
send_email.reset_mock()
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<p>Your project, {{ project.name }}, is currently using a legacy incoming webhook to trigger builds on Read the Docs. Effective April 1st, 2019, Read the Docs will no longer accept incoming webhooks through these endpoints.</p>

<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>

{% comment %}Plain text link because of text version of email{% endcomment %}
<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 number Diff line number Diff line change
@@ -0,0 +1 @@
Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after April 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>.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<p>Your project, {{ project.name }}, is currently using GitHub Services to trigger builds on Read the Docs. Effective January 31, 2019, GitHub will no longer process requests using the Services feature, and so Read the Docs will not receive notifications on updates to your repository.</p>

<p>To continue building your Read the Docs project on changes to your repository, you will need to add a new webhook on your GitHub repository. You can either connect your GitHub account and configure a GitHub webhook integration, or you can add a generic webhook integration.</p>

<p>You can find more information on our webhook intergrations in our documentation, at:</p>

{% comment %}Plain text link because of text version of email{% endcomment %}
<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 number Diff line number Diff line change
@@ -0,0 +1 @@
Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after January 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>.