github actions? actions ?action?

首先需要补充说明这几个名词,因为都和 action 相关容易让人混淆。

github actions :是对 gihub 所提供的 cicd 、test、build 和 deploy 功能的 workflow 的统称

actions: 指代每个项目中 github actions workflow 集合,每个项目都可以都有多个 yaml 描述文件,来实现不同的功能

action:在 github actions 实现了一个插件系统,允许用户自己实现,或是通过社区分享一些特殊的功能模块,由插件系统整合到 workflow 中,这些功能模块就叫 action

如何写一个基础的 action?

官方提供了一个基础的 github repo template https://github.com/actions/javascript-action

打开,点击 use this template,恭喜你已经实现了一个基础的延时功能 action 了。

好吧,当然这个 template 本身非常简单,但是她已经包含了开发一个 action 所必要知识,因此这里直接结合官方这个 template 解释 action 开发中的关键点吧

其:action 分类

首先, action 有两种分类

  1. JavaScript 脚本
  2. docker 镜像

显然支持 JavaScript 是为了迎合目前使用的人最多的脚本语言

而支持 docker 镜像,就提供更高一层的抽象,只要最终提供一个合法可运行的 docker 镜像,那么你容器内部做了什么事情,使用什么语言实现,悉听君便,非常自由。

其二:定义 action metadata

一个 action 必须要有一个 action.yml ,这个文件用于描述 action 的 Metadata

1
2
3
4
5
6
7
8
9
10
11
12
13
name: 'Wait'
description:0 'Wait a designated number of milliseconds'
inputs:
milliseconds: # id of input
description: 'number of milliseconds to wait'
required: true
default: '1000'
outputs:
time: # output will be available to future steps
description: 'The message to output'
runs:
using: 'node12'
main: 'dist/index.js'

其中比较关键的点是

  • inputs 指定 action 的入参,也就是在 workflow 中使用 with 关键字的指定的参数。
  • outpus 和 inputs 类似,指定一组参数,不过不同的是这些 outputs 在 action 执行完毕之后就可以在 workflow 的上下文中使用
  • runs 用于指定其运行的环境,入口文件等等

其他更多可定义 meta 数据可以看 https://help.github.com/cn/actions/building-actions/metadata-syntax-for-github-actions

其三:action-toolkit 官方工具套装

github 提供了一套官方 toolkit ,针对 action 在运行在 workflow 的环境内,可能会使用的一些操作如 io ,调用 child_process ,操作 github api 等等给出了相应的封装

例如 template 中的 index.js 可以看到的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const core = require('@actions/core');
const wait = require('./wait');


// most @actions toolkit packages have async methods
async function run() {
try {
const ms = core.getInput('milliseconds');
console.log(`Waiting ${ms} milliseconds ...`)

core.debug((new Date()).toTimeString())
await wait(parseInt(ms));
core.debug((new Date()).toTimeString())

core.setOutput('time', new Date().toTimeString());
}
catch (error) {
core.setFailed(error.message);
}
}

分别使用到了 @actions/core 提供的

  • getInput:获取入参
  • setOutput: 设定输出
  • setFailed:定义 action 失败

等功能。

https://github.com/actions/toolkit 查询到官方 toolkit 具体有哪些功能

小结

因此,一个 action 最核心的只有两个事情

  • action.yml Metadata 信息描述文件
  • 可执行的 js/docker 镜像

也因此,action不要求必须是一个单独的 repo ,也可以放在主 repo 的某目录下

在 workflow yaml 中直接通过

1
uses: ./action/

的方式引用。

这点在 template 的 workflow yaml 也可以看到,正是直接 uses:./ 调用自身作为 workflow 中的一个 action

自此已经知道了实现一个 action 的必要知识,剩下的限制就是你关于实现一个什么 action 的想象力问题了。

BTW

除了上述的一些内容,这里还有一些看相关内容是发现的有意思的点

  1. action 的 outputs inputs 其实都是直接挂在 process.env 上 https://github.com/actions/tolkit/blob/master/packages/core/src/core.ts#L71

  2. action-template 的 test 逻辑上有问题的,因为 l20 这里设置 process.env 只作用于当前进程,通过 cp.execSync 调用的子进程并不继承当前进程的 process.env 。所以正确的设置方法应该是

    1
    cp.execSync(`node ${ip}`, {env:{ INPUT_MILLISECONDS: 500 }})
  3. @action/core 中虽然有 setFailed 可以中断流程,但这是失败退出,因此还缺少一个安全退出流程的功能,用来控制 workflow,好消息是官方其实预留了一个坑位@action/exithttps://github.com/actions/toolkit/packages/12230 ,坏消息是这只是个空包

总结

这是第二篇 github actions 相关的文章

上篇重点在整个 workflow 的体验,这章则集中在其特色功能自定义的 action

总体来说 github action 还在一个非常基础的状态,因此相对简单许多。