章节 ▾ 第二版

6.5 GitHub - 脚本化 GitHub

脚本化 GitHub

到目前为止,我们已经涵盖了 GitHub 的所有主要功能和工作流程,但是任何大型团队或项目都会有他们可能想要进行的自定义或他们可能想要集成的外部服务。

幸运的是,GitHub 在许多方面确实非常容易被修改。在本节中,我们将介绍如何使用 GitHub 钩子系统及其 API,使 GitHub 按我们希望的方式工作。

服务和钩子

GitHub 仓库管理中的“钩子和服务”部分是让 GitHub 与外部系统交互的最简单方法。

服务

首先我们来看看服务。钩子和服务集成都可以在你的仓库的“设置”部分找到,我们之前在那里查看了添加协作者和更改项目的默认分支。在“Webhooks and Services”选项卡下,你会看到类似服务和钩子配置部分的内容。

Services and Hooks configuration section
图 129. 服务和钩子配置部分

有几十种服务可供选择,其中大多数是集成到其他商业和开源系统中的。其中大部分用于持续集成服务、bug 和问题跟踪器、聊天室系统和文档系统。我们将演示一个非常简单的设置,即 Email 钩子。如果你从“Add Service”下拉菜单中选择“email”,你将看到一个配置屏幕,如Email 服务配置所示。

Email service configuration
图 130. Email 服务配置

在这种情况下,如果我们点击“Add service”按钮,我们指定的电子邮件地址将在每次有人推送到仓库时收到一封电子邮件。服务可以监听许多不同类型的事件,但大多数只监听推送事件,然后利用这些数据做些事情。

如果你正在使用的系统想要与 GitHub 集成,你应该在这里查看是否有现有的服务集成可用。例如,如果你正在使用 Jenkins 在你的代码库上运行测试,你可以启用 Jenkins 内置服务集成,以便在每次有人推送到你的仓库时启动一次测试运行。

钩子

如果你需要更具体的功能,或者想与此列表中未包含的服务或网站集成,你可以改用更通用的钩子系统。GitHub 仓库钩子非常简单。你指定一个 URL,GitHub 就会在你想要的任何事件发生时向该 URL 发布一个 HTTP 有效载荷。

通常这种工作方式是你可以设置一个小型 Web 服务来监听 GitHub 钩子有效载荷,然后在接收到数据时对其进行处理。

要启用钩子,请点击服务和钩子配置部分中的“Add webhook”按钮。这将带你进入一个页面,看起来像Web 钩子配置

Web hook configuration
图 131. Web 钩子配置

Web 钩子的配置非常简单。在大多数情况下,你只需输入一个 URL 和一个密钥,然后点击“Add webhook”。对于你希望 GitHub 发送有效载荷的事件,有几个选项 — 默认是只在有人将新代码推送到你的仓库的任何分支时,获取 `push` 事件的有效载荷。

让我们看一个你可能设置来处理 web 钩子的小型 web 服务示例。我们将使用 Ruby web 框架 Sinatra,因为它相当简洁,你应该能够轻松理解我们在做什么。

假设我们希望在特定人员将更改推送到我们项目的特定分支并修改特定文件时收到电子邮件。我们可以通过以下代码轻松实现:

require 'sinatra'
require 'json'
require 'mail'

post '/payload' do
  push = JSON.parse(request.body.read) # parse the JSON

  # gather the data we're looking for
  pusher = push["pusher"]["name"]
  branch = push["ref"]

  # get a list of all the files touched
  files = push["commits"].map do |commit|
    commit['added'] + commit['modified'] + commit['removed']
  end
  files = files.flatten.uniq

  # check for our criteria
  if pusher == 'schacon' &&
     branch == 'ref/heads/special-branch' &&
     files.include?('special-file.txt')

    Mail.deliver do
      from     'tchacon@example.com'
      to       'tchacon@example.com'
      subject  'Scott Changed the File'
      body     "ALARM"
    end
  end
end

在这里,我们获取 GitHub 交付给我们的 JSON 有效载荷,并查找是谁推送的、他们推送到哪个分支以及所有已推送的提交中涉及哪些文件。然后,我们将这些信息与我们的标准进行核对,如果匹配则发送电子邮件。

为了开发和测试类似这样的东西,你在设置钩子的同一屏幕上有一个很好的开发者控制台。你可以看到 GitHub 尝试为该 webhook 进行的最后几次交付。对于每个钩子,你可以深入了解它何时交付、是否成功以及请求和响应的主体和头部。这使得测试和调试你的钩子变得异常容易。

Web hook debugging information
图 132. Web 钩子调试信息

这项功能的另一个优点是,你可以轻松地重新发送任何有效负载来测试你的服务。

有关如何编写 webhooks 以及你可以监听的所有不同事件类型的更多信息,请访问 GitHub 开发者文档:https://githubdocs.cn/en/webhooks-and-events/webhooks/about-webhooks

GitHub API

服务和钩子提供了一种接收关于仓库事件的推送通知的方式,但是如果你需要更多关于这些事件的信息呢?如果你需要自动化一些事情,比如添加协作者或标记问题呢?

这就是 GitHub API 派上用场的地方。GitHub 拥有大量的 API 端点,几乎可以自动完成你在网站上能做的任何事情。在本节中,我们将学习如何认证和连接到 API,如何对问题发表评论,以及如何通过 API 更改拉取请求的状态。

基本用法

最基本的操作是对不需要认证的端点进行简单的 GET 请求。这可以是关于开源项目的用户或只读信息。例如,如果我们想了解更多关于名为“schacon”的用户的信息,我们可以运行如下代码

$ curl https://api.github.com/users/schacon
{
  "login": "schacon",
  "id": 70,
  "avatar_url": "https://avatars.githubusercontent.com/u/70",
# …
  "name": "Scott Chacon",
  "company": "GitHub",
  "following": 19,
  "created_at": "2008-01-27T17:19:28Z",
  "updated_at": "2014-06-10T02:37:23Z"
}

有许多这样的端点可以获取关于组织、项目、问题、提交的信息——几乎所有你在 GitHub 上公开可见的信息。你甚至可以使用 API 来渲染任意 Markdown 或查找 `.gitignore` 模板。

$ curl https://api.github.com/gitignore/templates/Java
{
  "name": "Java",
  "source": "*.class

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear

# virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
"
}

对问题发表评论

但是,如果你想在网站上执行操作,例如评论 Issue 或 Pull Request,或者你想查看或与私有内容交互,你需要进行身份验证。

有几种认证方式。你可以只使用用户名和密码进行基本认证,但通常更好的做法是使用个人访问令牌。你可以在设置页面的“应用程序”选项卡中生成此令牌。

Generate your access token from the “Applications” tab of your settings page
图 133. 从设置页面的“应用程序”选项卡生成你的访问令牌

它会询问你希望此令牌具有哪些范围以及一个描述。请务必使用良好的描述,以便在你的脚本或应用程序不再使用时,你可以放心地删除该令牌。

GitHub 只会向你显示一次令牌,所以务必将其复制。现在你可以使用它在脚本中进行身份验证,而不是使用用户名和密码。这很好,因为你可以限制你想要做的事情的范围,并且令牌是可以撤销的。

这还具有增加你的速率限制的额外优势。未经身份验证,你将每小时被限制为 60 个请求。如果你进行身份验证,你每小时最多可以发出 5,000 个请求。

所以我们用它来评论我们的一个问题。假设我们想在特定的问题,问题 #6 上留下评论。为此,我们必须向 `repos///issues//comments` 发送一个 HTTP POST 请求,并将我们刚刚生成的令牌作为授权标头。

$ curl -H "Content-Type: application/json" \
       -H "Authorization: token TOKEN" \
       --data '{"body":"A new comment, :+1:"}' \
       https://api.github.com/repos/schacon/blink/issues/6/comments
{
  "id": 58322100,
  "html_url": "https://github.com/schacon/blink/issues/6#issuecomment-58322100",
  ...
  "user": {
    "login": "tonychacon",
    "id": 7874698,
    "avatar_url": "https://avatars.githubusercontent.com/u/7874698?v=2",
    "type": "User",
  },
  "created_at": "2014-10-08T07:48:19Z",
  "updated_at": "2014-10-08T07:48:19Z",
  "body": "A new comment, :+1:"
}

现在,如果你访问该问题,你将看到我们刚刚成功发布的评论,如通过 GitHub API 发布的评论所示。

A comment posted from the GitHub API
图 134. 通过 GitHub API 发布的评论

你可以使用 API 完成几乎所有你在网站上能做的事情——创建和设置里程碑、分配人员到 Issue 和 Pull Request、创建和更改标签、访问提交数据、创建新的提交和分支、打开、关闭或合并 Pull Request、创建和编辑团队、对 Pull Request 中的代码行发表评论、搜索网站等等。

更改拉取请求的状态

我们将看的最后一个例子,因为它在处理拉取请求时非常有用。每个提交都可以有一个或多个与之关联的状态,并且有一个 API 可以添加和查询该状态。

大多数持续集成和测试服务都利用这个 API 来响应推送,通过测试推送的代码,然后报告该提交是否通过了所有测试。你也可以用它来检查提交消息是否格式正确,提交者是否遵守了你的所有贡献指南,提交是否有效签名——任何数量的事情。

假设你在你的仓库上设置了一个 webhook,它会触发一个小型 web 服务,该服务会检查提交消息中是否存在 `Signed-off-by` 字符串。

require 'httparty'
require 'sinatra'
require 'json'

post '/payload' do
  push = JSON.parse(request.body.read) # parse the JSON
  repo_name = push['repository']['full_name']

  # look through each commit message
  push["commits"].each do |commit|

    # look for a Signed-off-by string
    if /Signed-off-by/.match commit['message']
      state = 'success'
      description = 'Successfully signed off!'
    else
      state = 'failure'
      description = 'No signoff found.'
    end

    # post status to GitHub
    sha = commit["id"]
    status_url = "https://api.github.com/repos/#{repo_name}/statuses/#{sha}"

    status = {
      "state"       => state,
      "description" => description,
      "target_url"  => "http://example.com/how-to-signoff",
      "context"     => "validate/signoff"
    }
    HTTParty.post(status_url,
      :body => status.to_json,
      :headers => {
        'Content-Type'  => 'application/json',
        'User-Agent'    => 'tonychacon/signoff',
        'Authorization' => "token #{ENV['TOKEN']}" }
    )
  end
end

希望这很容易理解。在这个 web 钩子处理程序中,我们遍历刚刚推送的每个提交,在提交消息中查找字符串 'Signed-off-by',最后通过 HTTP POST 将状态发送到 `/repos///statuses/` API 端点。

在这种情况下,你可以发送一个状态('success'、'failure'、'error'),一个关于发生情况的描述,一个用户可以获取更多信息的`target URL`,以及一个“context”(以防单个提交有多个状态)。例如,测试服务可能会提供一个状态,而像这样的验证服务也可能会提供一个状态——“context”字段就是它们之间的区别方式。

如果有人在 GitHub 上打开一个新的拉取请求并且此钩子已设置,你可能会看到类似通过 API 的提交状态的内容。

Commit status via the API
图 135. 通过 API 的提交状态

现在你可以看到消息中包含“Signed-off-by”字符串的提交旁边有一个绿色的小勾,而作者忘记签名的提交则有一个红色的叉。你还可以看到,拉取请求会获取分支上最后一次提交的状态,并在失败时警告你。如果你将此 API 用于测试结果,这非常有用,这样你就不会意外合并上次提交测试失败的内容。

Octokit

虽然在这些示例中,我们几乎所有操作都是通过 `curl` 和简单的 HTTP 请求完成的,但存在一些开源库,它们以更符合惯用语的方式提供此 API。在撰写本文时,支持的语言包括 Go、Objective-C、Ruby 和 .NET。请访问 https://github.com/octokit 了解更多信息,因为它们为你处理了大部分 HTTP 请求。

希望这些工具能帮助你自定义和修改 GitHub,使其更好地适应你的特定工作流程。有关整个 API 的完整文档以及常见任务指南,请访问 https://githubdocs.cn/