github action使用笔记

简述

Github Action 是什么?是 Github 推出的持续集成工具。

持续集成是什么?简单说就是自动化的打包程序——如果是前端程序员,这样解释比较顺畅:

每次提交代码到 Github 的仓库后,Github 都会自动创建一个虚拟机(Mac / Windows / Linux 任我们选),来执行一段或多段指令(由我们定),例如:

1
2
npm install
npm run build

所以 Action 功能其实就是对项目代码进行自动化测试,从而保证 push 代码的正确性。利用 Action 功能,你可以选择 Github 提供的各种测试环境 (windows, Linux, MaxOS) 运行你的项目。

而我们集成 Github Action 的做法,就是在我们仓库的根目录下,创建一个 .github 文件夹,里面放一个 *.yaml 文件——这个 Yaml 文件就是我们配置 Github Action 所用的文件。

Github Action 的使用限制

  • 每个 Workflow 中的 job 最多可以执行 6 个小时
  • 每个 Workflow 最多可以执行 72 小时
  • 每个 Workflow 中的 job 最多可以排队 24 小时
  • 在一个存储库的所有 Action 中,一个小时最多可以执行 1000 个 API 请求
  • 并发工作数:Linux:20,Mac:5(专业版可以最多提高到 180 / 50)

什么是 Workflow?Workflow 是由一个或多个 job 组成的可配置的自动化过程。我们通过创建 YAML 文件来创建 Workflow 配置。

Workflow 配置

名称

使用 name 参数定义 Workflow 的名称,Github 在存储库的 Action 页面上显示 Workflow 的名称。

如果我们省略 name,则 Github 会将其设置为相对于存储库根目录的工作流文件路径。

例如:

1
2
name: Greeting
on: push

触发器

on 用来配置触发 Workflow 执行的 event 名称,比如:每当我提交代码到 Github 上的时候

1
2
3
4
5
// 单个事件
on: push

// 多个事件
on: [push,pull_request]

事件名称索引:https://docs.github.com/cn/actions/using-workflows/events-that-trigger-workflows

job

首先要知道,一个 Workflow 由一个或多个 job 构成,含义是一次持续集成的运行,可以完成多个任务。

jobs 下定义的每一个 id 便与 job 一一对应,同时 jobname 属性用于显示在 Github 的 Action 页面中。

1
2
3
4
5
jobs:
my_first_job:
name: My first job
my_second_job:
name: My second job

上面的 my_first_jobmy_second_job 就是 jobid

而我们也可以定义 job 的依赖,通过 needs 属性配置。

所谓依赖,即如果依赖的 job 失败,则会跳过所有需要该 jobjob。(类似执行的前置条件)

1
2
3
4
5
6
jobs:
job1:
job2:
needs: job1
job3:
needs: [job1, job2]

jobs 的输出,用于和 needs 打配合:可以在 Action 页面中看到 output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
jobs:
job1:
runs-on: ubuntu-latest
# Map a step output to a job output
outputs:
output1: ${{ steps.step1.outputs.test }}
output2: ${{ steps.step2.outputs.test }}
steps:
- id: step1
run: echo "::set-output name=test::hello"
- id: step2
run: echo "::set-output name=test::world"
job2:
runs-on: ubuntu-latest
needs: job1
steps:
- run: echo ${{needs.job1.outputs.output1}} ${{needs.job1.outputs.output2}}

注意到上面的 runs-on 属性可以用于指定 job 的运行环境,而 Github 上可用的运行环境有以下:

runs-on-support
1
2
3
4
5
jobs:
job1:
runs-on: macos-10.15
job2:
runs-on: windows-2019

如果有必要的话,我们还可以定义常量(环境变量)

1
2
3
4
jobs:
job1:
env:
HANDSOMEMAN: qsdz

我们还可以使用 if 条件语句来组织 job 运行。

每个 job 由多个 step 构成,它会从上至下依次执行。

step 可以运行:

  1. commands:命令行命令
  2. setup tasks:环境配置命令(比如安装个 Node 环境、安装个 Python 环境)
  3. action(in your repository, in public repository, in Docker registry):一段 Action

每个 step 都在自己的运行器环境中运行,并且可以访问工作空间和文件系统。

因为每个 step 都在运行器环境中独立运行,所以 step 之间不会保留环境变量的更改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 定义 Workflow 的名字
name: Greeting
# 定义 Workflow 的触发器
on: push
# 定义 Workflow 的 job
jobs:
# 定义 job 的 id
my-job:
# 定义 job 的 name
name: My Job
# 定义 job 的运行环境
runs-on: ubuntu-latest
# 定义 job 的运行步骤
steps:
# 定义 step 的名称
- name: Print a greeting
# 定义 step 的环境变量
env:
GREET: Hello!
MSG: World!
# 运行指令:输出环境变量
run: |
echo $GREET $MSG

而如果我们需要运行命令行命令,我们需要使用 run 参数

run 命令在默认状态下会启动一个没有登录的 shell 来作为命令输入器。

每个 run 命令都会启动一个新的 shell,所以我们执行多行连续命令的时候需要写在同一个 run 下:

  • 单行命令
1
2
- name: Install Dependencies
run: npm install
  • 多行命令
1
2
3
4
- name: Clean install dependencies and build
run: |
npm ci
npm run build

使用 working-directory 关键字,我们可以指定 command 的运行位置:

1
2
3
- name: Clean temp directory
run: rm -rf *
working-directory: ./temp

使用 shell 关键字,来指定特定的 shell:

1
2
3
4
steps:
- name: Display the path
run: echo $PATH
shell: bash

下面是各个系统支持的 shell 类型:

shell-support

就是有时候,我们的代码可能编译环境有多个。比如 electron 的程序,我们需要在 macos 上编译 dmg 压缩包,在 windows 上编译 exe 可执行文件。

这个时候,我们需要用到矩阵(strategy)这个功能。

比如下面的代码,我们使用了矩阵指定了:2 个操作系统,3 个 node 版本

这时候下面这段代码就会执行 6 次(2 个操作系统 x 3 个 node 版本)。

1
2
3
4
5
6
7
8
9
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-16.04, ubuntu-18.04]
node: [6, 8, 10]
steps:
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}

Action

Action 其实就是命令,比如 Github 官方给了我们一些默认的命令:https://github.com/marketplace?type=actions&query=actions

比如最常用的,check-out 代码到 Workflow 工作区:https://github.com/marketplace/actions/checkout

比如我们可以 check-out 仓库中最新的代码到 Workflow 的工作区:

1
2
steps:
- uses: actions/checkout@v2

当然,我们还可以给它添加个名字:

1
2
3
steps:
- name: Check out Git repository
uses: actions/checkout@v2

也可以 Action 参数

1
2
3
4
5
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2-beta
with:
node-version: '12'

其中 @v2@v2-beta 的意思都是 Action 的版本。

我们如果不带版本号的话,其实就是默认使用最新版本的了。

但是 Github 官方强烈要求我们带上版本号——这样子的话,我们就不会出现:写好一个 Workflow,但是由于某个 Action 的作者一更新,我们的 Workflow 就崩了的问题。

with 中是一些特殊的 Action 所需要的参数,具体需要什么要在对应的 Action 官方页面中查看。

有关更多请参考 https://docs.github.com/cn/actions/using-workflows/workflow-syntax-for-github-actions

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
name: Trigger workflow with issue

on: [issues]
permissions:
issues: write
jobs:
start:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- run: echo "${{ secrets.FLAG1 }}" > flag
- run: 'echo "${{ github.event.issue.body }}" > tmpfile'
end:
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- run: echo https://api.github.com/repos/${{github.repository}}/issues/${{github.event.issue.number}}
- run: |
curl \
-X PATCH \
-H "Authorization: token ${{ github.token }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{github.repository}}/issues/${{github.event.issue.number}} \
-d '{"title":"This is new title", "body": "action run finished"}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
name: Clean the logs of the run
on:
# Allows you to run this workflow manually from the Actions tab
workflow_run:
workflows: [Trigger workflow with issue]
types:
- completed

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
clean:
# The type of runner that the job will run on
runs-on: ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/github-script@v6
with:
script: |
github.rest.actions.deleteWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id
})