Skip to content

Don't trigger a sync twice on creation/deletion for GitHub #6614

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 10 commits into from
Feb 18, 2020
26 changes: 23 additions & 3 deletions readthedocs/api/v2/views/integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ def get_integration(self):
integration = Integration.objects.create(
project=self.project,
integration_type=self.integration_type,
provider_data={},
# If we didn't create the integration,
# we didn't set a secret.
secret=None,
Expand Down Expand Up @@ -196,12 +197,19 @@ def get_response_push(self, project, branches):
'versions': list(to_build),
}

def sync_versions(self, project):
version = sync_versions(project)
def sync_versions(self, project, sync=True):
"""
Trigger a sync and returns a response indicating if the build was triggered or not.

If `sync` is False, the sync isn't triggered and a response indicating so is returned.
"""
version = None
if sync:
version = sync_versions(project)
return {
'build_triggered': False,
'project': project.slug,
'versions': [version],
'versions': [version] if version else [],
'versions_synced': version is not None,
}

Expand Down Expand Up @@ -379,6 +387,18 @@ def handle_webhook(self):
raise ParseError('Parameter "ref" is required')
# Sync versions on other PUSH events that create or delete
elif event in (GITHUB_CREATE, GITHUB_DELETE, GITHUB_PUSH):
if event == GITHUB_PUSH:
# GitHub will send push and create/delete events on a creation/deletion.
# If we receive a push event we need to check if the webhook doesn't
# already have the create/delete events. So we don't trigger the sync twice.
# We listen to push events for creation/deletion for old webhooks only.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good comment 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow this code 😢

We are already handling GITHUB_PUSH in the previous if with deleted or created (line 382), why don't we just remove the GITHUB_PUSH from this elif (line 389) instead? That seems to do the same without extra code.

Like this on line 389:

elif event in (GITHUB_CREATE, GITHUB_DELETE):
    return self.sync_versions(self.project)

That way, we are only syncing versions on CREATE and DELETE, but not on regular PUSH.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what we want is:

  • GITHUB_PUSH and (created or deleted) -> sync versions
  • GITHUB_CREATE or GITHUB_DELETE -> sync versions
  • all the other possibilities -> do not sync versions

If this is correct, I do see this logic easier to understand than our current implementation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are already handling GITHUB_PUSH in the previous if with deleted or created

We are triggering a build in that case, and we check for not (deleted or created)

why don't we just remove the GITHUB_PUSH from this elif (line 389) instead?

We want to listen to PUSH events for sync, but only trigger a sync if the webhook isn't subscribed to creation/deletion events.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub will send two events when a branch/tag is created/deleted (if the webhook is listening to creation/deletion events), that's why we need to discard the push event in that case.

GITHUB_PUSH and (created or deleted) -> sync versions

That will still need to check if the webhook isn't subscribed to creation/deletion. Otherwise it'll trigger two syncs.

GITHUB_CREATE or GITHUB_DELETE -> sync versions

This will trigger a second sync for users that have webhooks listening to creation/deletion events.

integration = self.get_integration()
events = integration.provider_data.get('events', [])
if (
(created and GITHUB_CREATE in events) or
(deleted and GITHUB_DELETE in events)
):
return self.sync_versions(self.project, sync=False)
return self.sync_versions(self.project)

elif (
Expand Down
50 changes: 50 additions & 0 deletions readthedocs/rtd_tests/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1345,6 +1345,56 @@ def test_github_skip_signature_validation(self, trigger_build):
)
self.assertEqual(resp.status_code, 200)

def test_github_dont_trigger_double_sync(self, trigger_build):
"""Don't trigger a sync twice if the webhook has the create/delete events."""
integration = Integration.objects.create(
project=self.project,
integration_type=Integration.GITHUB_WEBHOOK,
provider_data={
'events': [
GITHUB_CREATE,
GITHUB_DELETE,
],
},
secret=None,
)

client = APIClient()

headers = {
GITHUB_EVENT_HEADER: GITHUB_PUSH,
}
payload = {
'ref': 'master',
'created': True,
'deleted': False,
}
resp = client.post(
reverse(
'api_webhook_github',
kwargs={'project_slug': self.project.slug}
),
payload,
format='json',
**headers
)
self.assertFalse(resp.json()['versions_synced'])

headers = {
GITHUB_EVENT_HEADER: GITHUB_CREATE,
}
payload = {'ref': 'master'}
resp = client.post(
reverse(
'api_webhook_github',
kwargs={'project_slug': self.project.slug}
),
payload,
format='json',
**headers
)
self.assertTrue(resp.json()['versions_synced'])

def test_gitlab_webhook_for_branches(self, trigger_build):
"""GitLab webhook API."""
client = APIClient()
Expand Down