Webhook + Flask 实现 Code Review 自动化

这是一篇流水账技术笔记,大概也许只有我自己能看懂

什么是 Webhook?

官方的语言解释 Webhook(来自 coding.net):

Webhook 允许第三方应用监听 Coding.net 上的特定事件,在这些事件发生时通过 HTTP POST 方式通知( 超时5秒) 到第三方应用指定的 Web URL。例如项目有新的内容 Push,或是 Merge Request 有更新等。 Coding.net 用户可以在自己的项目 → 设置 → Webhook 中创建、设置 Webhook 所需监听的事件,并配置第三方应用的 Web URL 。WebHook 可方便用户实现自动部署,自动测试,自动打包,监控项目变化等

通俗一点,可以将 Webhook 理解为一个勤勤恳恳的“卧底”,它被你安插到对手的队伍中,一旦对方有任何动静,它会第一时间把“情报”发送给你。所以,一旦“卧底”成功打入(配置应用 URL),你就可以远程监视对手的一举一动(HTTP POST 或 PUT 方法)了。

Photo on unsplash by Hoach Le Dinh

Photo on unsplash by Hoach Le Dinh

为什么要 Code Review 任务分配自动化?

任务的大致描述是,在团队项目中,除了主分支之外成员还会建立多个分支,当分支要与主分支合并时,通常需要审核分支的代码才能确认是否合并,也就是 code review。

那为什么要需要 code review?一,merge 发起者需要手动在网页上操作才能完成“分配”的动作,并且需要额外思考需要将任务分配给谁,次数多了就比较麻烦;二,有助于团队形成统一的代码规范。所以, 不如让其自动化随机分配,大家谁也怨谁,最后还可以提升自己代码审查的能力。这个想法可以通过 webhook + Flask 实现,思路如下图:

webhook + Flask 实现 code review 自动分配

webhook + Flask 实现 code review 自动分配

在思路的实现中,需要关注三个参数的获取:project_id assignee_id merge_request_iid 的获取。

Code Review 任务自动化分配的实现平台将以 GitLab 为例,并且需要大量调用 GitLab 的 API 以获取必要的参数。目前参数的调用有两种方法,一种是通过 Python 封装好的 python-gitlab 库进行调用,一种是通过请求 gitlab 的 GitLab API。这两种方法功能上可以互补,相互结合使用,因为毕竟通过封装好的库,使用更加方便。

1. python-gitlab 库

在这一小节主要介绍在实现自动化过程中需要用到的一些参数获取方法。

初始化一个 project 变量

1
2
3
4
5
6
7
8
import gitlab
project_url = 'http://X.X.X.X'
# 在 gitlab 的 profile->account 中可以找到,唯一
token = 'yourtoken'
# 初始化 project
gl = gitlab.Gitlab(project_url, token)
gl.auth()

1.1 获取你所有参与的项目 id

调用该方法会列出你目前所有参与项目的 id,有了这个 id 才能获得某个项目下的详细信息,包括成员信息、项目描述、merge request 提交情况。

1
2
projects = gl.projects.list()
print(projects)

1.2 由 project_id 获取某项目下成员的信息

1
2
3
4
5
6
7
project = gl.projects.get(project_id)
# 获取该项目下所有成员信息
me = project.members.list()
# 获取项目下所有成员 id 列表
member = get_member_id(project)
# 打印第一个成员的姓名
print(me[0].name)

1.3 由 project_id 获取某项目下 merge request 提交信息

1
2
3
4
5
6
7
8
9
10
project = gl.projects.get(project_id)
mrs = project.mergerequests.list()
# 打印最近一次 merge request 信息
print(mrs[0])
# 获取发起 merge request 者 ID
print(mrs[0].id)
# 获取 merge request iid
# 这个 id 在更新 merge request 的时候会用到
# 这个参数很关键
print(mrs[0].iid)

1.4 Update MR

当用户发起 merge request,发起者会将 compare and continue 的任务分配给一个成员。现在的任务是,通过调用 GitLab 的 API 来更新改动后的 merge request。在 Python-gitlab 库里边,更新 merge request 步骤如下:

1
2
3
4
5
6
7
# Get a single MR
mr = project.mergerequests.get(mr_id)
# Update a MR
# 上一个变量 mr 有许多 key,可以通过修改 key 下
# 的 value 实现修改 merge request 信息
mr.description = 'New description'
mr.save()

不过貌似这里有一个 bug,保存之后 merge request 的信息 并没有即时更新,所以通过这个方法更新 merge request 的想法就终止了。

到此为止,我们已经在 1.3 中得到了 merge request iidassignee_id(有了 merge person idmember_list 写个函数可以得到,注意把 merge_person_id 排除)。还有最后一个 project_id 的获取将要在后面的 flask 中实现。

Photo on unsplash by Thomas Hetzler

Photo on unsplash by Thomas Hetzler

2. GitLab API

通过 requests 库调用 GitLab 的 API

Update MR

由于通过 python-gitlab 库暂时没有实现更新功能,我们转向 GitLab 的 Update MR API ,详细的 API 说明见 Update MR

1
PUT /projects/:id/merge_requests/:merge_request_iid
GitLab Update MR API 参数说明

GitLab Update MR API 参数说明

在 Python 中实现该过程,需要通过 request 发起一个 HTTP PUT 请求,且至少需要 project idmerge request iid 两个参数,目前两个参数通过 Python-gitlab 库已经轻松得到。

在 Python 下,通过 requests 库发起一个 PUT 请求(requests.put(url, data=payload, headers=headers)),然后在 gitlab 的信息流中就会出现 merge request 的消息。在这个过程中,有两个地方需要注意:

一、一定要注意请求的参数是 merge_request_iid (一般只有一位,如 1 或 6)而不是 merge_request_id(有四位,如 1526),否则无法发起 PUT 请求,会返回 401 错误或 404 错误,正常情况下应该返回 200

二、assignee_id 是通过 data 参数传入,private-token 需要写入到头部信息 headers

另外一些 no required 的参数则是你需要按需添加的了,比如本例中要选择任务执行人(assignee_id)。一旦 PUT 请求成功后,你的 GitLab 主页便会收到任务提示信息。

1
2
3
4
5
6
7
8
9
import requests
headers = {"Private-Token": 'your-private-token'}
payload = {
"assignee_id": [member_id]
}
r = requests.put(""http://x.x.x.x/api/v4/projects/" + str(project_id) + "/merge_requests/" + str(merge_request_iid)",
data=payload, headers=headers)

3. Flask

经过上面的介绍,我们知道这几个参数是如何获取的,接下来就需要在项目中设定一个 webhook 了,即“安插卧底”。首先在项目的集成页面开启一个 webhook,设置好服务器地址和需要监控的行为。

GitLab 开启 webhook

GitLab 开启 webhook

手动测试 webhook

手动测试 webhook

然后还需要将配置信息用 flask 写成一个简单的应用,最后在服务器中运行该服务就可以监控每次 merge request 动作,并随机将 code review 任务分配给成员。其中,flask 会监控到 GitLab 收到的 POST 请求能够得到相应的数据,在返回的 body 中可以将 project_id 获取。到此为止,三个参数全部收集完毕。

1
2
3
4
body = request.data.decode('utf-8')
params = json.loads(body)
project_info = params.get("object_attributes", "")
project_id = project_info['source_project_id']

PS:将 app.run() 改为 app.run(debug=True) 即可。这样每次修改代码之后,不需要每次都重启服务器。

1
2
3
4
5
6
7
8
9
10
from flask import Flask
from flask import request
@app.route('/', methods=['POST'])
def index():
body = request.data.decode('utf-8')
··········
if __name__ == '__main__':
app.run(debug=True, port=8001, host="0.0.0.0")

总的来说,GitLab 让用户可以设置 webhook 以监控用户的某些行为,这个想法可以用 flask 包装成为一个后台应用。

由于某些原因,完整的代码没有全部给出,相信这些教程网上应该大把大把的有。如果大家感兴趣的话可以给我发邮件交流详细的实现过程。后续还有一些比较好玩的地方,比如调用外部语音合成接口,每当任务分配完成后,语音播报具体的情况,这个应该会比较好玩。总之,你能想到干什么,GitLab 提供的 API 基本上都能帮你实现。

其他

1
2
3
# 在浏览器中直接输入,获取成员的信息
# get memebers' information
http://x.x.x.x/api/v3/users?secret-token=XXXXXXXXXXXXX
1
2
3
4
# 通过命令行获取
curl --header "PRIVATE-TOKEN: XXXXXXXXXXXXX" http://XXX.XXX.XXX.XXX/api/v4/groups/:id/members/:user_id
curl --header "PRIVATE-TOKEN: your-token" http://XXX.XXX.XXX.XXX/api/v4/projects/:project_id/members/:member_id

获取 gitlab 项目 ID,通过命令行返回用户建立的 projects 或 groups 的信息

1
curl -XGET --header "PRIVATE-TOKEN: a994TnhXxoLMHHE_vgHw" "http://x.x.x.x/api/v3/projects/owned"

推荐阅读

  1. coding.net:webhook 介绍
  2. GitLab documentation: Update MR
  3. Flask 快速入门中文文档
觉得还不错?帮我赞助点域名费吧:)