hacker0limbo / my-blog

个人博客
5 stars 1 forks source link

简单聊一聊 github action #24

Open hacker0limbo opened 3 years ago

hacker0limbo commented 3 years ago

简单聊一聊 github action

Github Action 是 Github 官方出的持续集成服务, 挺早之前就推出了, 这次正好遇到一点需求, 看了一下文档自己写了一个 workflowaction 脚本

文档还是很全的, 但是细节有点多, 写的时候不注意的话很容易踩坑, 而且这个东西无法在本地进行调试, 我只能每次更新了代码后手动 run 一次 workflow, 虽然有一个叫 act 的库貌似支持本地跑的, 但是又要用到 docker 啥的, 我电脑比较旧带不动, 就算了

基本概念

阮一峰老师写过一篇还挺清晰的教程介绍基本概念: GitHub Actions 入门教程. 最基本的几个概念为:

以官方给的一个 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 的含义为:

触发 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.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'

说明一下:

关于 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:

这里注意一下, 由于在使用第三个 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/nccaction 脚本进行打包, 这么做也是为了避免上传 node_modules. 或者另一种做法是在 workflow 跑的时候手动安装需要的依赖. 这里我选择前一种方法. 这里 npm i -g @vercel/ncc 后使用 ncc build action.js 即可打包文件到 dist/index.js 中. 遗憾的是貌似无法指定对应的文件名.

参考