简单聊一聊 github action
hacker0limbo opened this issue · 0 comments
简单聊一聊 github action
Github Action 是 Github 官方出的持续集成服务, 挺早之前就推出了, 这次正好遇到一点需求, 看了一下文档自己写了一个 workflow
和 action
脚本
文档还是很全的, 但是细节有点多, 写的时候不注意的话很容易踩坑, 而且这个东西无法在本地进行调试, 我只能每次更新了代码后手动 run 一次 workflow
, 虽然有一个叫 act 的库貌似支持本地跑的, 但是又要用到 docker 啥的, 我电脑比较旧带不动, 就算了
基本概念
阮一峰老师写过一篇还挺清晰的教程介绍基本概念: GitHub Actions 入门教程. 最基本的几个概念为:
- workflow
- event
- job
- step
- action
以官方给的一个 workflow 文件为例, 所有的 workflow 都存放在 .github/workflows
目录下:
# .github/workflows/learn-github-actions.yml
name: learn-github-actions
on: [push]
jobs:
check-bats-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
该 workflow 的含义为:
- 在
push
事件触发时激活该 workflow - 一共有一个 job, 名字为
check-bats-version
, 该 job 跑在最新的 ubuntu 上 - 该 job 一共有 2 个 step, 每个 step, 对应一个 action
- 其中第二个
actions/setup-node@v2
action 运行时需要传递参数, 参数名node-version
, 参数值14
触发 workflow 的事件还有很多, 可以是多个事件, 也可以手动触发, 也可以细分具体到一个事件的不同类型, 具体需要参考官方文档: Events that trigger workflows
关于 workflow 的语法具体也参考官方文档: Workflow syntax for GitHub Actions
需求
虽然 github marketplace 有很多已经写好的 action, 但是还是免不了自己有特质需求需要自定义 action. 比如我的需求是, 我的博客全部写在 issue 里, README.md
通常作为目录按照时间分类放置每篇文章的标题和对应到 issue 的链接, 大致格式是这样:
# 个人博客
## 2019
- [第一篇博客](https://github.com/owner/repo/issues/1)
- [第二篇博客](https://github.com/owner/repo/issues/2)
## 2020
...
但是每次更新 issue 之后就需要手动去更新 README, 我所希望的时候能有 workflow 能帮我自动处理这些事情, 在博客更新之后通过 github 提供的 API 手动提交一次 commit 更新我的 README 目录
实现
action.yml 和 workflow.yml
官方有关于自定义 action 的文档: About custom actions. 以及如何自定义 JavaScript action: Creating a JavaScript action 还算详细. 不过有一些细节需要注意:
- action 命名一定是
action.yml
或者是action.yaml
- 自定义 action 的文件位置一般放在根目录下, 当然放在
.github
文件夹下也行, 不过在workflow
的时候还需要 checkout 定位到 repo 上. 而且只有在根目录下的 action 才能发布到 github marketplace.
action.yml
的大致语法可以参考: Metadata syntax for GitHub Actions. 这里简略罗列一下我的:
name: 'Action Name'
description: 'Description for custom action'
inputs:
repoToken:
description: 'github access token'
required: true
runs:
using: 'node12'
main: 'dist/index.js'
说明一下:
name
和descript
分别是自定义 action 的名字和描述inputs
是该 action 的输入源, 可以有多个. 由于我的需求需要用到 github 的 API, 使用过程需要 token 来 authentication. 这个 token 每次 workflow 运行时会自动生成, 不需要手动输入. 因此在编写 workflow 的时候要注明. 这里我给的输入源名字为repoToken
runs
注明了使用node12
, 同时对应的 JavaSctipt action 文件为根目录下的dist/index.js
关于 token, 官方有提到: Automatic token authentication. 如文中所述, 在编写 workflow 时, 需要使用 with 语法提供对应的参数, 这个参数就是自动生成的 token.
对应我的 workflow 文件内容如下:
name: My Workflow
on:
workflow_dispatch:
issues:
types: [opened, edited, deleted]
jobs:
sync-readme:
runs-on: ubuntu-latest
name: My job name
steps:
- name: use my custom action
uses: owner/repo@master
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
这里 on
监听了两个事件, 一个是 workflow_dispatch
, 一个是 issues
具体的类型. workflow_dispatch
是为了允许手动跑 workflow, 方便线上调试. 具体可以看: Manually running a workflow. issues 我提供了三个类型分别在新增一条 issue, 编辑一条 issue 和删除一条 issue 时触发该 workflow.
最后是绑定自定义的 action, 注意这里 with
下的 repoToken
和上面 action.yml
中的 inputs
里对应
编写脚本
需要用到两个库:
第一个库主要用于获取输入输出, 比如上文提到的 token. 打日志. 第二个库提供了 Github API 的接口, 返回的是一个已经 auth 过的 Octokit REST 客户端
由于我的需求是根据在我更新完 issue 之后根据最新的 issue 信息更新我的 README. 所有思路也很简单, 发一次请求获取所有的 issue 信息, 然后再根据 issue 信息做一次 commit 更新 readme. 需要用到 3 个 API:
octokit.rest.issues.listForRepo
octokit.rest.repos.getContent
octokit.rest.repos.createOrUpdateFileContents
这里注意一下, 由于在使用第三个 api 即 createOrUpdateFileContents
时候, 如果是更新文件内容需要提供该文件的 sha
值, 所以只能先通过 getContent
获取到 sha
值之后再更新, 同时更新的内容必须使用 Base64
encoding 加密.
最后的文件大体思路为:
const core = require('@actions/core');
const github = require('@actions/github');
const { Buffer } = require('buffer');
function run() {
const repoToken = core.getInput('repoToken');
const octokit = github.getOctokit(repoToken);
octokit.rest.issues
.listForRepo({
owner: 'xxx',
repo: 'yyy',
})
.then(({ data: issues }) => {
const content = contentFromIssues(issues)
octokit.rest.repos
.getContent({
owner: 'xxx',
repo: 'yyy',
filePath: 'README.md'
})
.then(({ data }) => {
const { sha } = data
octokit.rest.repos
.createOrUpdateFileContents({
...config,
path: 'README.md',
message: 'feat: xxx',
content: Buffer.from(content).toString('base64'),
sha,
})
.then((res) => core.info('success to update'))
.catch(error => core.setFailed(error.response.data.message))
})
.catch(error => core.setFailed(error.response.data.message))
})
.catch(error => core.setFailed(error.response.data.message))
}
run()
最后, 官方文档建议使用 @vercel/ncc 对 action
脚本进行打包, 这么做也是为了避免上传 node_modules
. 或者另一种做法是在 workflow 跑的时候手动安装需要的依赖. 这里我选择前一种方法. 这里 npm i -g @vercel/ncc
后使用 ncc build action.js
即可打包文件到 dist/index.js
中. 遗憾的是貌似无法指定对应的文件名.