Skip to content

Commit 753f35a

Browse files
authored
Remove worker (and fix readmen + dockercompose.yml); add sync_plans and pulls_sync tasks; finish gh webhook handler. (codecov#144)
1 parent 1282d66 commit 753f35a

File tree

9 files changed

+130
-35
lines changed

9 files changed

+130
-35
lines changed

.gitmodules

-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +0,0 @@
1-
[submodule "worker"]
2-
path = worker
3-
url = https://github.com/codecov/worker

Makefile

-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ ssh_private_key = `cat ~/.ssh/codecov-io_rsa`
33
build:
44
docker build -f Dockerfile . -t codecov/api:latest --build-arg SSH_PRIVATE_KEY="${ssh_private_key}"
55

6-
standalone: build
7-
$(MAKE) -C worker build
8-
96
test:
107
python -m pytest --cov=./
118

README.md

+3-5
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@ A private Django REST Framework API intended to serve codecov's front end.
77

88
### Building
99

10-
This project contains a makefile. To build in standalone mode, run:
10+
This project contains a makefile. To build the docker image:
1111

12-
make standalone
13-
14-
To build with codecovio (as a submodule) the `build` make target is included
12+
make build
1513

1614
`requirements.txt` is used in the base image. If you make changes to `requirements.txt` you will need to rebuild.
1715

@@ -25,7 +23,7 @@ To start the service, do
2523

2624
Utilizing its own database provides a convenient way for the REST API to provide its own helpful seeds and migrations for active development without potentially destroying/modifying your development database for codecov.io.
2725

28-
Once running, the api will be available at `http://localhost:8000`
26+
Once running, the api will be available at `http://localhost:5100`
2927

3028
### Running with codecov.io
3129

docker-compose.yml

-14
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,6 @@ services:
3535
- redis-volume:/data
3636
networks:
3737
- codecovapi
38-
worker-new:
39-
image: codecov/worker
40-
build: ./worker
41-
working_dir: /app/
42-
entrypoint: sh worker.sh
43-
volumes:
44-
- ./worker:/app/
45-
environment:
46-
- CELERY_BROKER_URL=redis://redis:6379/0
47-
- CELERY_RESULT_BACKEND=redis://redis:6379/0
48-
networks:
49-
- codecovapi
50-
depends_on:
51-
- redis
5238

5339
volumes:
5440
postgres-volume:

services/task.py

+19
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ def notify(self, repoid, commitid):
3535
kwargs=dict(repoid=repoid, commitid=commitid)
3636
).apply_async()
3737

38+
def pulls_sync(self, repoid, pullid):
39+
self._create_signature(
40+
'app.tasks.pulls.Sync',
41+
kwargs=dict(
42+
repoid=repoid,
43+
pullid=pullid,
44+
)
45+
).apply_async()
46+
3847
def refresh(self, ownerid, username, sync_teams=True, sync_repos=True, using_integration=False):
3948
"""
4049
!!!
@@ -69,3 +78,13 @@ def refresh(self, ownerid, username, sync_teams=True, sync_repos=True, using_int
6978
))
7079

7180
return chain(*chain_to_call).apply_async()
81+
82+
def sync_plans(self, sender=None, account=None, action=None):
83+
self._create_signature(
84+
'app.tasks.ghm_sync_plans.SyncPlans',
85+
kwargs=dict(
86+
sender=sender,
87+
account=account,
88+
action=action
89+
)
90+
)

webhook_handlers/constants.py

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class GitHubWebhookEvents:
1515
INSTALLATION = "installation"
1616
INSTALLATION_REPOSITORIES = "installation_repositories"
1717
ORGANIZATION = "organization"
18+
MARKETPLACE_PURCHASE = "marketplace_purchase"
19+
MARKETPLACE_SUBSCRIPTION = "marketplace_subscription"
1820

1921
repository_events = [PULL_REQUEST, DELETE, PUSH, PUBLIC, STATUS, REPOSITORY]
2022

webhook_handlers/tests/test_github.py

+72-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import uuid
22
import pytest
3+
import hmac
4+
from hashlib import sha1
5+
import json
36

47
from unittest.mock import patch, call
58

@@ -11,6 +14,7 @@
1114
from core.models import Repository
1215
from codecov_auth.models import Owner
1316
from codecov_auth.tests.factories import OwnerFactory
17+
from utils.config import get_config
1418

1519
from webhook_handlers.constants import GitHubHTTPHeaders, GitHubWebhookEvents, WebhookHandlerErrorMessages
1620

@@ -22,7 +26,15 @@ def _post_event_data(self, event, data={}):
2226
**{
2327
GitHubHTTPHeaders.EVENT: event,
2428
GitHubHTTPHeaders.DELIVERY_TOKEN: uuid.UUID(int=5),
25-
GitHubHTTPHeaders.SIGNATURE: 0
29+
GitHubHTTPHeaders.SIGNATURE: 'sha1='+hmac.new(
30+
get_config(
31+
"github",
32+
'webhook_secret',
33+
default=b'testixik8qdauiab1yiffydimvi72ekq'
34+
),
35+
json.dumps(data, separators=(',', ':')).encode('utf-8'),
36+
digestmod=sha1
37+
).hexdigest(),
2638
},
2739
data=data,
2840
format="json"
@@ -371,9 +383,26 @@ def test_pull_request_exits_early_if_repo_not_active(self):
371383
assert response.status_code == status.HTTP_200_OK
372384
assert response.data == WebhookHandlerErrorMessages.SKIP_NOT_ACTIVE
373385

374-
@pytest.mark.xfail
375-
def test_pull_request_triggers_pulls_sync_task_for_valid_actions(self):
376-
assert False
386+
@patch('services.task.TaskService.pulls_sync')
387+
def test_pull_request_triggers_pulls_sync_task_for_valid_actions(self, pulls_sync_mock):
388+
pull = PullFactory(repository=self.repo)
389+
390+
valid_actions = ["opened", "closed", "reopened", "synchronize"]
391+
392+
for action in valid_actions:
393+
response = self._post_event_data(
394+
event=GitHubWebhookEvents.PULL_REQUEST,
395+
data={
396+
"repository": {
397+
"id": self.repo.service_id
398+
},
399+
"action": action,
400+
"number": pull.pullid
401+
}
402+
)
403+
404+
pulls_sync_mock.assert_has_calls([call(repoid=self.repo.repoid, pullid=pull.pullid)] * len(valid_actions))
405+
377406

378407
def test_pull_request_updates_title_if_edited(self):
379408
pull = PullFactory(repository=self.repo)
@@ -552,3 +581,42 @@ def test_membership_with_removed_action_removes_user_from_org(self):
552581
user.refresh_from_db()
553582

554583
assert org.ownerid not in user.organizations
584+
585+
@patch('services.task.TaskService.sync_plans')
586+
def test_marketplace_subscription_triggers_sync_plans_task(self, sync_plans_mock):
587+
sender = {
588+
"id": 545,
589+
"login": "[email protected]"
590+
}
591+
action = "purchased"
592+
account = {
593+
"type": "Organization",
594+
"id": 54678,
595+
"login": "username"
596+
}
597+
response = self._post_event_data(
598+
event=GitHubWebhookEvents.MARKETPLACE_PURCHASE,
599+
data={
600+
"action": action,
601+
"sender": sender,
602+
"marketplace_purchase": {
603+
"account": account
604+
}
605+
}
606+
)
607+
608+
sync_plans_mock.assert_called_once_with(sender=sender, account=account, action=action)
609+
610+
def test_signature_validation(self):
611+
response = self.client.post(
612+
reverse("github-webhook"),
613+
**{
614+
GitHubHTTPHeaders.EVENT: '',
615+
GitHubHTTPHeaders.DELIVERY_TOKEN: uuid.UUID(int=5),
616+
GitHubHTTPHeaders.SIGNATURE: 0
617+
},
618+
data={},
619+
format="json"
620+
)
621+
622+
assert response.status_code == status.HTTP_403_FORBIDDEN

webhook_handlers/views/github.py

+34-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import logging
22
import re
3+
import hmac
4+
from hashlib import sha1
35

46
from rest_framework.views import APIView
57
from rest_framework.permissions import AllowAny
68
from rest_framework.response import Response
79
from rest_framework import status
10+
from rest_framework.exceptions import PermissionDenied
811

912
from core.models import Repository, Branch, Commit, Pull
1013
from codecov_auth.models import Owner
1114
from services.archive import ArchiveService
1215
from services.redis import get_redis_connection
1316
from services.task import TaskService
17+
from utils.config import get_config
1418

1519
from webhook_handlers.constants import GitHubHTTPHeaders, GitHubWebhookEvents, WebhookHandlerErrorMessages
1620

@@ -32,7 +36,18 @@ class GithubWebhookHandler(APIView):
3236
redis = get_redis_connection()
3337

3438
def validate_signature(self, request):
35-
pass
39+
sig = 'sha1='+hmac.new(
40+
get_config(
41+
"github",
42+
'webhook_secret',
43+
default=b'testixik8qdauiab1yiffydimvi72ekq'
44+
),
45+
request.body,
46+
digestmod=sha1
47+
).hexdigest()
48+
49+
if sig != request.META.get(GitHubHTTPHeaders.SIGNATURE):
50+
raise PermissionDenied()
3651

3752
def unhandled_webhook_event(self, request, *args, **kwargs):
3853
return Response(data=WebhookHandlerErrorMessages.UNSUPPORTED_EVENT)
@@ -141,7 +156,7 @@ def pull_request(self, request, *args, **kwargs):
141156
action, pullid = request.data.get("action"), request.data.get("number")
142157

143158
if action in ["opened", "closed", "reopened", "synchronize"]:
144-
pass # TODO: should trigger pulls.sync task
159+
TaskService().pulls_sync(repoid=repo.repoid, pullid=pullid)
145160
elif action == "edited":
146161
Pull.objects.filter(
147162
repository=repo, pullid=pullid
@@ -205,11 +220,25 @@ def organization(self, request, *args, **kwargs):
205220

206221
return Response()
207222

223+
def _handle_marketplace_events(self, request, *args, **kwargs):
224+
TaskService().sync_plans(
225+
sender=request.data["sender"],
226+
account=request.data["marketplace_purchase"]["account"],
227+
action=request.data["action"]
228+
)
229+
return Response()
230+
231+
def marketplace_subscription(self, request, *args, **kwargs):
232+
return self._handle_marketplace_events(request, *args, **kwargs)
233+
234+
def marketplace_purchase(self, request, *args, **kwargs):
235+
return self._handle_marketplace_events(request, *args, **kwargs)
236+
208237
def post(self, request, *args, **kwargs):
209238
self.validate_signature(request)
210239

211-
self.event = self.request.META.get(GitHubHTTPHeaders.EVENT)
212-
log.info("GitHub Webhook Handler invoked with: %s", self.event.upper())
213-
handler = getattr(self, self.event, self.unhandled_webhook_event)
240+
event = self.request.META.get(GitHubHTTPHeaders.EVENT)
241+
log.info("GitHub Webhook Handler invoked with: %s", event.upper())
242+
handler = getattr(self, event, self.unhandled_webhook_event)
214243

215244
return handler(request, *args, **kwargs)

worker

-1
This file was deleted.

0 commit comments

Comments
 (0)