@github_app.on(["check_run.created"])
def initiate_check_run():
"""Start the CI process"""
# Check that the event is being sent to this app
if str(github_app.payload["check_run"]["app"]["id"]) == config.GITHUB_APP_ID:
client = github_app.github_app_installation.get_github_client()
repo = client.get_repo(github_app.payload["repository"]["full_name"])
check_run = repo.get_check_run(github_app.payload["check_run"]["id"])
# Mark the check run as in process
check_run.edit(
name=APP_NAME,
status="in_progress",
started_at=datetime.now(),
)
# ***** RUN A CI TEST *****
# 暂略
# Mark the check run as complete!
check_run.edit(
name=APP_NAME,
status="completed",
completed_at=datetime.now(),
conclusion=conclusion
)
# lint
max_annotations = 50
annotations = []
# RuboCop reports the number of errors found in "offense_count"
if len(output) == 0:
conclusion = "success"
actions = None
else:
conclusion = "neutral"
for file in output:
file_path = re.sub(f"{repo_dir}/{repository}/", "", file["path"])
annotation_level = "notice"
# Parse each offense to get details and location
# Limit the number of annotations to 50
if max_annotations == 0:
break
max_annotations -= 1
start_line = file["line"]
end_line = file["line"]
start_column = file["column"]
end_column = file["column"]
message = file["message"]
# Create a new annotation for each error
annotation = {
"path": file_path,
"start_line": start_line,
"end_line": end_line,
"start_column": start_column,
"end_column": end_column,
"annotation_level": annotation_level,
"message": message,
}
# # Annotations only support start and end columns on the same line
# if start_line == end_line:
# annotation.merge({"start_column": start_column, "end_column": end_column})
annotations.append(annotation)
# Need fix action
actions = [
{
"label": "Fix this",
"description": "Automatically fix all linter notices.",
"identifier": "fix_rubocop_notices",
}
]
步骤 2.5. 使用 CI 测试结果更新检查运行
整理结果,并添加修复动作:
summary = (
f"Summary\n"
f"- Offense count: {len(output)}\n"
f"- File count: {len(set([file['path'] for file in output]))}\n"
)
text = "Octo Pylinter version: pylint"
# Mark the check run as complete!
check_run.edit(
name=APP_NAME,
status="completed",
completed_at=datetime.now(),
conclusion=conclusion,
output={
"title": "Octo Pylinter",
"summary": summary,
"text": text,
"annotations": annotations,
},
actions=actions,
)
这篇博客的起因是在做项目的过程中要求使用 Python 完成相应功能,现在将这部份代码按教程的流程发布出来。
原文《使用 Checks API 创建 CI 测试》中使用 Ruby,现使用 Python 完成文档示例。由于教程已经将大部分内容详细描述了,本文只列出与原来教程有不同的步骤,以及对应的 Python 代码。
项目地址:qiwihui/githubappcheckruns
基本要求
文档:https://docs.github.com/cn/developers/apps/setting-up-your-development-environment-to-create-a-github-app
访问 smee.io 并创建一个新的 channel,比如 https://smee.io/LgDQ8xrhy0q2GeET,然后使用
pysmee
命令运行如下命令:或者使用项目目录 smee 中的 node 脚本运行
第 1 部分 创建检查 API 接口
步骤 1.1. 更新应用程序权限
主要为以下权限:
步骤 1.2. 添加事件处理
对应于 Ruby 中使用 Sinatra 作为 web 框架,我们使用
Flask
作为 web 框架,并结合PyGithub
这个库提供的 github API 封装,由于 PyGithub 在发布的版本中还未集成 check run 对应的 API,所以使用其master
分支上的代码,添加git+https://github.com/PyGithub/PyGithub.git
到 requirements.txt 中。其中,
GithubAppFlask
提供三个功能:on
装饰器,对于不同 github 动作分发处理;步骤 1.3. 创建 check run
使用 PyGithub 库的
create_check_run
处理步骤 1.4. 更新 check run
第 2 部分 创建 Octo RuboCop CI 测试
原教程使用 RuboCop 作为 ruby 代码语法检查和格式化工具,相对应,我们使用
pylint
作为 python 代码语法检查,使用autopep8
作为格式化工具。 同样,对于git项目的操作,我们使用GitPython
简化操作。步骤 2.1. 添加 Python 文件
添加要操作的 python 文件即可。
步骤 2.2. 克隆仓库
使用 GitPython 库处理,使用临时目录进行克隆。
运行 CI 测试:
步骤 2.3. 运行 pylint
pylint 运行并输出json结果。
步骤 2.4. 收集 pylint 错误
pylint结果与
rubocop
类似,收集并解析结果:步骤 2.5. 使用 CI 测试结果更新检查运行
整理结果,并添加修复动作:
步骤 2.6. 自动修复错误
沿用
fix_rubocop_notices
这个 ID,使用autopep8
做 python 文件的修正,将结果以 PR 的方式提交。在以上步骤的基础上,可以构建更复杂的测试过程,完成不同的需求。