用 Django 来响应 Github Webhook

瞳人


发布于 June 20, 2016, 4:44 p.m.

16 个评论

Django Github


利用 Django 来响应 Github 的 webhook 请求.

由于本 vps 上的 gitlab 开销大,所以我把所有项目移到 github 上。因此原先针对 gitlab 的 webhook 代码就需要更新了。

关于 github webhook 的介绍,可以看后文的参考链接,这里就简单叙述一下解决方案。

  1. 当你向你 github 中的 repo 添加一个新的 webhook 时,github 会给你发一个 ping 事件的 post 请求, 这时你就可以在数据库中登记这个webhook。

  2. 你可以在 github 上设置触发该 webhook 的事件类型,我这里就设置了 push 事件。

  3. 为了安全起见,github 可以设置一个密钥,然后你可以在响应代码中进行验证。

  4. 同样的,与之前 gitlab 的 webhook 代码 中一样,我们现在数据库中添加一些需要验证的信息,包括 repository 的名字,ssh clone 地址即 'git@github.com:user/repo.git',secret,repository 的 id 等,但在初始化时我们先把 repo_id 填成 -1,这个值稍后由代码更新。

代码

models.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# coding=utf-8
from django.db import models
from django.utils.translation import ugettext as _

class Github_Webhook(models.Model):
    ''' Model for webhook of github

    '''
    repo_name = models.CharField(
        verbose_name = _(u'repository name'),
        help_text = _(u' '),
        max_length = 255
    )
    repo_ssh_url = models.CharField(
        verbose_name = _(u'repository ssh_url'),
        help_text = _(u' '),
        max_length = 255
    )
    repo_id = models.IntegerField(
        verbose_name = _(u'repository id value'),
        help_text = _(u'Your repository id in github'),
        default = 0
    )
    secret = models.CharField(
        verbose_name = _(u'repository secret'),
        help_text = _(u' '),
        max_length = 255
    )

    def __unicode__(self):
        return u'%s' % (self.repo_name,)

views.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@csrf_exempt
def github_webhook(request):
    if request.method == 'POST' and request.body:
        http_x_github_event = request.META.get('HTTP_X_GITHUB_EVENT', '')
        http_x_hub_signature = request.META.get('HTTP_X_HUB_SIGNATURE', '')
        json_data = json.loads(request.body)
        repo_data = json_data.get('repository', '')
        sender_data = json_data.get('sender', '')
        if repo_data and sender_data and http_x_hub_signature:
            user_id = sender_data.get('id', '')
            user_name = sender_data.get('login', '')
            repo_name = repo_data.get('name', '')
            repo_id = repo_data.get('id', '')
            repo_ssh_url = repo_data.get('ssh_url', '')
            sha_name, signature = http_x_hub_signature.split('=')
            if sha_name != 'sha1':
                return HttpResponseForbidden('HeHe!')
            webhook = Github_Webhook.objects.filter(
                repo_name=repo_name,
                repo_ssh_url=repo_ssh_url
            ).first()
            if webhook:
                # HMAC requires the key to be bytes, but data is string
                mac = hmac.new(str(webhook.secret), msg=request.body, digestmod=sha1)
                # Python prior to 2.7.7 does not have hmac.compare_digest
                if hexversion >= 0x020707F0:
                    if not hmac.compare_digest(str(mac.hexdigest()), str(signature)):
                        return HttpResponseForbidden('HeHe!')
                else:
                    # What compare_digest provides is protection against timing
                    # attacks; we can live without this protection for a web-based
                    # application
                    if not str(mac.hexdigest()) == str(signature):
                        return HttpResponseForbidden('HeHe!')
                if webhook.repo_id == -1 and http_x_github_event == 'ping':
                    # Save this webhook
                    webhook.repo_id = int(repo_id)
                    webhook.save()
                    content = u'Save github webhook: [%s] by user %s, %s' % (
                        webhook, user_id, user_name)
                    write_log(content)
                    return HttpResponse('Webhook saved!')
                if webhook.repo_id == int(repo_id) and http_x_github_event == 'push':
                    # Do your webhook job
                    # such as restarting a docker container.
                    content = u'Restart github webhook: [%s] by user %s, %s' % (
                        webhook, user_id, user_name)
                    write_log(content)
                    return HttpResponse('Restarted!')

    return HttpResponseForbidden('HeHe!')

参考链接

  1. 我的博客 用 Django 来响应 Gitlab Webhook

  2. carlos-jenkins/python-github-webhooks

  3. Github webhook 官方介绍

  4. Github Event Types & Payloads 官方介绍


哎呦, 不错哦!

16 Comments

eddie June 23, 2016, 11:26 p.m. | Reply

你好,看了你的《Django 博客评论系统替代多说 (二)》,感觉收获很多。但是遇到一个问题,就是写好之后,runserver,会出现:
django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet
网上搜了一圈也没有找到解决方法,如果我把mycomments里面的__init__.py中的内容都删掉,就不会出现上面的错误了,但是会出现别的错误。。。
还望博主不吝赐教,感谢感谢~~

eddie June 27, 2016, 6:53 p.m. | Reply

你好!
我的django版本是1.9.2,python版本是3.3

瞳人 June 28, 2016, 10:50 a.m. | Reply

不好意思啊,我这个评论框好久没有改过了,所以回复起来比较麻烦。。
我django用的是 1.8,python 2。但是看起来不像是版本问题。
你在添加这个 mycomments 这个app 之前都是正常的吗?
是不是在 settings.py 里面加错了 app?

瞳人 June 28, 2016, 10:52 a.m. | Reply

我自己先试一下 python 3 和 django 1.9。。

瞳人 June 28, 2016, 11:14 a.m. | Reply

和 python 3 应该没关系,但是和 django 1.9 有关系。我看见说 In Django 1.9, you shouldn't import models from your app's __init__.py file.
我有空把我的环境升级一下到 django 1.9 看看。

eddie June 28, 2016, 2:02 p.m. | Reply

多谢博主,等待好消息~
我现在随便用了一个友言评论,但是比较死板。还是自己写的靠谱,还能多练练手。

瞳人 June 28, 2016, 4 p.m. | Reply

不用谢。我最近在准备 GRE,所以空余时间不是很多。
我发现一件悲剧的事情,qq邮箱又开始封 mailgun 发送的邮件了。
所以评论不能及时送达我qq邮箱了。
对了,你现在的博客地址是啥,我们交流交流。

eddie June 28, 2016, 5:46 p.m. | Reply

yangystudio.applinzi.com
也不算博客,是女朋友做的微信号,我借机做个网站,练练手。django+bootstrap,都是最基础的东西,我是菜鸟~

瞳人 June 28, 2016, 6:46 p.m. | Reply

哇!我看了,很不错啊!加油!

瞳人 June 28, 2016, 6:51 p.m. | Reply

不过我发现一个问题,就是你网页上面的导航栏上的 active class 加的不对。yangystudio.applinzi.com 进去应该是首页,但是你 active 加在的主页上,这个 active应该是指示当前所在页面的。

eddie June 28, 2016, 7:34 p.m. | Reply

喔,active原来是这个意思,我还担心用户不知道怎么进入“主页”,所以把“主页”标记了一下,哈哈

瞳人 June 29, 2016, 3:29 p.m. | Reply

我看了一下 django 1.8 是 LTS 版本,而1.9不是,所以我暂时并不打算升级到1.9。因为好久没有维护代码了,我打算先按照 django 1.8 来重构一下代码,然后再看看依据1.9要做些什么改变。

eddie June 29, 2016, 3:40 p.m. | Reply

嗯嗯,我可以把django版本从1.9降回1.8么?

瞳人 June 29, 2016, 3:41 p.m. | Reply

我看你网站是放在 sae 上面的,我不知道这个是怎么做的。你项目本来就是 1.9 的还是 1.8 的?

瞳人 June 30, 2016, 11:46 a.m. | Reply

我今天把博客python环境升级到3.4了,过两天从django1.8升级到1.9

瞳人 June 30, 2016, 7:54 p.m. | Reply

我已经解决了这个问题,这个问题和 python3 没有关系,是和 django 1.9 有关系,请参考文章 http://answ.me/post/comment-in-django-part-2/ 中新的 __init__.py 代码即可。


Leave a Comment:

博客搜索

友情链接

公告

本博客代码已经公布在 Github 上,欢迎交流指正。

QQ 邮箱对 mailgun 不太友好, 所以使用 QQ 邮箱的评论, 可能会无法及时收到邮件。我会尽快寻找其他解决方案的。

本人现在独自使用 linode vps, 20 美元/月, 感觉压力大, 如果有意一起合租, 可以联系我. 在我的任意一篇文章下面留言即可. 关于使用方式, 现在倾向于使用 docker.