完整GitHub-Actions和工作流指南

每个 action 就是一个独立脚本,因此可以做成代码仓库,使用 userName/repoName 的语法引用 action。比如: actions/setup-node 就表示 github.com/actions/setup-node 这个仓库,它代表一个 action,作用是安装 Node.js。事实上,GitHub 官方的 actions 都放在 github.com/actions里面了,另外,还有一个 awesome actions 的仓库,也可以找到不少 action。既然 actions 是代码仓库,当然就有版本的概念,用户可以引用某个具体版本的 action。下面都是合法的 action 引用,用的就是 Git 的指针概念,详见官方文档

Github Action 官方文档说明

actions/setup-node@74bc508 # 指向一个 commit
actions/[email protected] # 指向一个标签
actions/setup-node@master # 指向一个分支

VsCode编写Github Action准备工作

GitHub 工作流以YAML 格式编写,而YAML 格式只是一种数据序列化语言,与Json非常相似。

编写vscode配置文件以格式化 YAML 代码

在要编写的项目下面新建一个 .vscode 文件夹,在文件夹下 新建 settings.json 文件,编写如下代码:

{
"editor. fontsize": 18,
"window. zoomLevel": 4,
"workbench. activityBar. visible": false,
"workbench. statusBar. visible": false,
"gitlens. currentLine. enabled": false,
"editor. formatonsave": true,
"terminal. integrated. fontsize": 18,
"terminal. integrated. fontWeight": "500",
"terminal. integrated. fontWeightBold": "600"
}

再在此文件夹下新建第二个文件 .prettierrc 编写如下代码:

{
"arrowParens": "avoid",
"bracketspacing": true,
"htmlwhitespacesensitivity": "css",
"insertPragma": false,
"jsxBracketsameLine": false,
"jsxsingleQuote": false,
"printwidth": 100,
"proselrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": false,
"tabwidth": 4,
"trailingcomma": "nопe",
"useTabs": false,
"vueIndentscriptAndstyle": false
}

但是为了使用上面的代码,还必须安装更漂亮的扩展,VS Code扩展。Prettier - Code formatter v11.0.0 ,和 YAMLv1.15.0 ,以自动完成并自动验证编写的YAML文件并显示错误和类似内容。

还可以安装一个 YAML 语法到 JSON 语法的扩展,可以将 YAML转换为JSON。 YAML to JSONv0.0.6
在vscode 中点击Ctrl+shift + p 打开 vscode 命令栏 输入 yaml to json 点击 YAML to JSON: Convert selection or document 就可以将 yaml 转为 json 格式。

基本概念

GitHub Actions 有一些自己的术语。
(1)workflow (工作流程):持续集成一次运行的过程,就是一个 workflow。
(2)job (任务):一个 workflow 由一个或多个 jobs 构成,含义是一次持续集成的运行,可以完成多个任务。
(3)step(步骤):每个 job 由多个 step 构成,一步步完成。
(4)action (动作):每个 step 可以依次执行一个或多个命令(action)。

workflow 文件

GitHub Actions 的配置文件叫做 workflow 文件,存放在代码仓库的 .github/workflows 目录。workflow 文件采用 YAML 格式,文件名可以任意取,但是后缀名统一为 .yml,比如 foo.yml。一个库可以有多个 workflow 文件。GitHub 只要发现 .github/workflows 目录里面有 .ym 文件,就会自动运行该文件。workflow 文件的配置字段非常多,详见官方文档。下面是一些基本字段。

name 字段 workflow 的名称

name 字段是 workflow 的名称。如果省略该字段,默认为当前 workflow 的文件名。
name: Github Actions Demo

on 指定触发 workflow 的条件

触发 Workflow 执行的 event 名称,比如:每当我提交代码到 Github 上的时候,on: push
或者是每当我打 TAG 的时候,都可以触发Workflow事件的执行。触发事件大全
单个事件:on: push 。 多个事件: on: [push,pull_request]
cron 定时触发:

schedule:
# 分钟 小时 天 月 周
# https://crontab.guru/
- cron: "* * * * * "

除了代码库事件,GitHub Actions 也支持外部事件触发:
repository_dispatch 对仓库发送post 的url :
(“repository_url”: “https://api.github.com/repos/{owner}/{repo}/dispatches“) 数据请求时 触发 。

on: 
repository_dispatch:
types: [build]

这个post的请求需要 token 令牌作为 url请求的密码发能成功。
github令牌

post :https://api.github.com/repos/jinwei26/testAction/dispatches
postman发送请求时的各详细参数:

Accept: application/vnd.github.everest-preview+json
headers参数

"event_type": "build"
也可以是:

{
"event_type": "build",
"client_payload": {
"env": "production"
}
}

body参数

这里是填入 上面获取的 Github 令牌的 token。
github令牌token

repository_dispatch的使用还可以参考 我的 另外一篇文章 repository_dispatch的使用教程

指定触发事件时,可以限定分支或标签

on: 
push:
branches:
- master
- 'feature/*' # 一个* 和 2个 ** 的区别
- 'feature/**'
tags:
- v1.*
paths:
# 每当我们推送一个JavaScript 文件时,这个工作流就会运行。
- '**.js'

具体 1个 ‘*‘ 和 2 个’**‘的区别请查看 官方文档:You can use special characters in path, branch, and tag filters
上面代码指定,只有master分支发生push事件时,才会触发 workflow。

指定触发事件时,我们也可以 忽略 限定分支或标签

on: 
push:
branches-ignore:
- master
- 'feature/*' # 一个* 和 2个 ** 的区别
- 'feature/**'
paths:
# ! + 文件名 这个工作也会被忽略
- '!filename.js'
paths-ignore:
# 如果有任何内容被推送到这个doc 路径时,这个工作流不会运行,被忽略
- 'doc/**'

jobs 字段

workflow 文件的主体是jobs字段,表示要执行的一项或多项任务。jobs 字段里面,需要写出每一项任务的job_id,具体名称自定义。job_id 里面的 name 字段是任务的说明。如:

jobs:
my_first_job:
name: My first job
my_second_job:
name: My second job

上面代码的 jobs 字段包含两项任务,job_id 分别是 my_first_job 和 my_second_job。

needs 字段指定当前任务的依赖关系,即运行顺序。

jobs:
job1:
job2:
needs: job1
job3:
needs: [job1, job2]

上面代码中,job1必须先于job2完成,而job3等待job1和job2的完成才能运行。因此,这个 workflow 的运行顺序依次为:job1、job2、job3。

runs-on 字段指定运行所需要的虚拟机环境。它是必填字段。目前可用的虚拟机如下。

ubuntu-latest,ubuntu-18.04或ubuntu-16.04
windows-latest,windows-2019或windows-2016
macOS-latest或macOS-10.14

下面代码指定虚拟机环境为ubuntu-18.04。

runs-on: ubuntu-18.04

环境变量-在变量中存储信息 .env

name: 环境变量测试
on: push
env: # 全局环境变量
WF_ENV: Available to all jobs

jobs:
log-env:
runs-on: ubuntu-latest
env:
# 只对log-env 工作起作用的环境变量
JOB_ENV: Available to all steps in log-env job
steps:
- name: Log ENV Available
# 只对 这个步骤起作用的环境变量
env:
STEP_ENV: Available to only this step
run: |
echo "WF_ENV: ${WF_ENV}"
echo "JOB_ENV: ${JOB_ENV}"
echo "STEP_ENV: ${STEP_ENV}"
- name: Log ENV 2
run: |
echo "WF_ENV: ${ WF_ENV }"
echo "JOB_ENV: ${ JOB_ENV }"
echo "STEP_ENV: ${ STEP_ENV }" # 这个不会回显
# 由GitHub自动设置并且我们可以使用的默认环境变量。
log-default-env:
runs-on: ubuntu-latest
steps:
- name: Default ENV Variables
run: |
echo "HOME: ${HOME}" # 虚拟机上主目录的路径
echo "GITHUB_WORKFLOW: ${GITHUB_WORKFLOW}" # 工作流名称
echo "GITHUB_ACTION: ${GITHUB_ACTION}"
echo "GITHUB_ACTIONS: ${GITHUB_ACTIONS}"
echo "GITHUB_ACTOR: ${GITHUB_ACTOR}"
echo "GITHUB_REPOSITORY: ${GITHUB_REPOSITORY}"
echo "GITHUB_EVENT_NAME: ${GITHUB_EVENT_NAME}"
echo "GITHUB_WORKSPACE: ${GITHUB_WORKSPACE}"
echo "GITHUB_SHA: ${GITHUB_SHA}"
echo "GITHUB_REF: ${GITHUB_REF}"
echo "WF_ENV: ${WF_ENV}"
echo "JOB_ENV: ${JOB_ENV}" # 这个不会显示
echo "STEP_ENV: ${STEP_ENV}" #这个不会显示

具体的请参阅 官方的 GitHub 为每个 GitHub Actions 工作流运行设置默认变量。 你还可以设置自定义变量,以便在单个工作流或多个工作流中使用

Action 中 Secrets 的使用

在GitHub Actions 中创建加密的环境变量
在项目的 Settings ——> Secrets and variables –> Action –>Secrets–>New repository has on secrets –>Name 这个变量名可以随便取 –> Secret 这里面填上你的变量 这样就可以在Action里面调用了。
加密

GITHUB_TOKE 它包含一个Github令牌,可以使用它来调用Github的 API 或将某些内容推送到您的存储库之类的 具体查看官方文档

Github 的令牌和 项目的令牌以及环境变量,只能在里面设置密钥,不能设置大小超过64k以上的文件加密,所以就利用 The GNU Privacy Guard) Gpg4win - Secure email and file encryption with GnuPG for Windows 对文件进行加密上传到存储库,然后利用Action虚拟机,因为虚拟机中已经安装了gpg ,所以我们可以在虚拟机中进行解密,官方文档,操作代码如下:

本地加密命令:gpg --symmetric --cipher-algo AES256 my_secret.json

name: 64k以上加密文件解密
on: push

jobs:
decrypt:
runs-on: ubuntu-latest
steps:
# 第一步我们需要克隆我们的仓库到虚拟机
- uses: actions/checkout@v2
- name: 解密文件 # gpg 已经安装在了虚拟机上
# 通过设置gpg参数 解密 虚拟机上的gpg加密文件,解密后放在主目录HOME下
run: gpg --quiet --batch --yes --decrypt --passphrase="$GPG_SECRET" --output $HOME/my_secret.json my_secret.json.gpg
env:
GPG_SECRET: ${{ secrets.GPG_SECRET}} # 密钥给 变量
- name: 打印解密的内容
# cat 接收文件路径的bash命令 它只输出该文件的内容
run: cat $HOME/my_secret.json

steps 字段指定每个 Job 的运行步骤,可以包含一个或多个步骤。每个步骤都可以指定以下三个字段。

obs.<job_id>.steps.name:步骤名称。
jobs.<job_id>.steps.run:该步骤运行的命令或者 action。
jobs.<job_id>.steps.env:该步骤所需的环境变量。

下面是一个完整的 workflow 文件的范例。

name: Greeting from Mona
on: push

jobs:
my-job:
name: My Job
runs-on: ubuntu-latest
steps:
- name: Print a greeting
env:
MY_VAR: Hi there! My name is
FIRST_NAME: Mona
MIDDLE_NAME: The
LAST_NAME: Octocat
run: |
echo $MY_VAR $FIRST_NAME $MIDDLE_NAME $LAST_NAME.

上面代码中,steps字段只包括一个步骤。该步骤先注入四个环境变量,然后执行一条 Bash 命令。

# 定义 Workflow 的名字
name: Greeting from Mona

# 定义 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:
MY_VAR: Hi there! My name is
FIRST_NAME: Mona
MIDDLE_NAME: The
LAST_NAME: Octocat
# 运行指令:输出环境变量
run: |
echo $MY_VAR $FIRST_NAME $MIDDLE_NAME $LAST_NAME.

Action 的作用 :我们可以直接打开 Action 市场来看看:Action 其实就是命令,比如 Github 官方 给了我们一些默认的命令:最常用的,check-out 代码到 Workflow 工作区: 使用 uses 来执行这些 Action 命令。

steps:
- uses: actions/checkout@v2

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

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

再比如说,我们如果是 node 项目,我们可以安装 Node.js 与 NPM:

steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2-beta
with: # 指明安装的条件
node-version: '12'

上面这个 @v2 和 @v2-beta 的意思都是 Action 的版本,我们如果不带版本号的话,其实就是默认使用最新版本的了。但是 Github 官方强烈要求我们带上版本号——这样子的话,我们就不会出现:写好一个 Workflow,但是由于某个 Action 的作者一更新,我们的 Workflow 就崩了的问题。
有的 Action 可能会需要我们传入一些特定的值:比如上面的 node 版本啊之类的,这些需要我们传入的参数由 with 关键字来引入。具体库的使用和参数,我们可以去官方的 Action 市场 查看。

run 命令的使用 :run 命令在默认状态下会启动一个没有登录的 shell 来作为命令输入器。每个 run 命令都会启动一个新的 shell,所以我们执行多行连续命令的时候需要写在同一个 run 下:

# 单命令:
- name: Install Dependencies
run: npm install
# 多行命令
- name: Clean install dependencies and build
run: |
npm ci
npm run build

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

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

shell 的类型有 cmd or powershell or python 等,如何指定 shell 的执行类型呢?我们可以使用 关键字 shell 来指定特定的 shell 类型。

steps:
- name: Display the path
run: echo $PATH
shell: bash

在Action 中使用 函数

函数的具体使用请查看官网说明,这里只介绍个别的函数使用方法。
以下示例程序来自 官网代码

name: Context testing
on: [push,pull_request]
# contains 检查第二个参数中的字符串是否包含在第一个参数中的字符串中
# startsWith 检查一个字符串是否以另一个字符串开头
jobs:
functions:
# 这里不能使用 ubuntu-16.04 使用了运行不了
runs-on: ubuntu-latest
steps:
- name: dump
run: |
echo ${{ contains( 'hello','ll')}}
echo ${{ startsWith( 'hello','he')}}
echo ${{ endsWith('hello','lo')}}
echo ${{ format('hello {0} {1} {2}','word','!','!')}}
dump_contexts_to_log:
runs-on: ubuntu-latest
# 如果是 pull 下面的不会被执行
if: github.event_name == 'push'
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }} # 使用toJson函数
run: eccho "$GITHUB_CONTEXT" # 制造一个错误 eccho
- name: Dump job context
# failure 只会在上一步失败时返回 true
if: failure()
env:
JOB_CONTEXT: ${{ toJson(job) }}
run: echo "$JOB_CONTEXT"
- name: Dump steps context
env:
STEPS_CONTEXT: ${{ toJson(steps) }}
run: echo "$STEPS_CONTEXT"
- name: Dump runner context
# 下面的代码总会被执行
if: always()
env:
RUNNER_CONTEXT: ${{ toJson(runner) }}
run: echo "$RUNNER_CONTEXT"
- name: Dump strategy context
env:
STRATEGY_CONTEXT: ${{ toJson(strategy) }}
run: echo "$STRATEGY_CONTEXT"
- name: Dump matrix context
env:
MATRIX_CONTEXT: ${{ toJson(matrix) }}
run: echo "$MATRIX_CONTEXT"

如果上一步失败了,但是想要继续执行下面的步骤,也可以将 continue-on-error: true 其设置为 true,即使第一步失败,所有其他步骤也会继续执行的,如果设置为 false 其他步骤也不会执行,默认为 false。
timeout-minutes 超时 Github Action执行时长默认为360分钟,即 6 个小时。也可以自行设定超时退出,但不能超过 6 个小时。

name: print
on: push
env:
continue: true
time: 3
jobs:
job1:
runs-on: ubuntu-latest
steps:
- continue-on-error: ${{ fromJSON(env.continue) }}
timeout-minutes: ${{ fromJSON(env.time) }}
run: echo ...

该工作流使用 fromJSON 函数将环境变量从字符串转换为布尔值,从而允许它确定是否在出错时继续。同样,它将环境变量从字符串转换为整数,以分钟为单位设置作业的超时。fromJSON() continue time.

策略选项 strategy.matrix

我们可以在特定作业中指定运行的选项,这个选项将允许我们设置一个环境矩阵。假设我们想在不同的环境中运行我们的工作,如:windows虚拟机、ubuntu、不同的nodejs版本等待,使用这个策略选项就会变得非常容易。

name: Matrix-矩阵
on: push

jobs:
node-version:
# 添加 矩阵 键 strategy.Matrix
strategy:
matrix: # 三乘三矩阵 2个数组,每个3个元素 运行 9 次
os: [macos-latest,ubuntu-latest,windows-latest]
node_version: [6,8,10] # 定义任何名称的一个数组,数组值为版本号
include: # 包含的意思
- os: ubuntu-latest
node_version: 8
is_ubuntu_8: "true"
exclude: # 排除 版本号为 6 的ubuntu-latest
- os: ubuntu-latest
node_version: 6
- os: macos-latest
node_version: 8
# max-parallel: 0 # 三个作业将并行运行,不管哪个出错
# 快速失败选项,如果这些作业之一失败,则其他作业都将停止
# 如果为 false 则每个作业都将独立于其他作业的结果运行
# fail-fast: true # 暂时不需要 另用 最大并行 max-parallel: 0
runs-on: ${{ matrix.os }} # ubuntu-latest
env:
IS_UBUNTU_8: ${{ matrix.is_ubuntu_8 }} # 添加一个环境变量
steps:
- name: 记录 node 版本
run: node -v #显示当前版本
# 更换使用的 node 版本
- uses: actions/setup-node@v1
# with 是条件 指定 node 的版本号
with: # 利用上下文表达式替换下面的 版本 6
node-version: ${{ matrix.node_version }} # 将 '10.x' 改为你想要的版本 如:6
- name: 显示 node 版本号
run: |
node -v
echo $IS_UBUNTU_8

上面的例题中使用了2个一维数创建一个三乘三矩阵,每个3个元素共 运行 9 次,其中穿插了包含和排除选项和 fail-fast 快速失败选项,max-parallel 最大并行数等。具体的情况请查看官网matrix contest文档

Github Action中Docker的使用

name: container-容器
on: push

jobs:
docker-steps:
runs-on: ubuntu-latest
container:
image: node:20-alpine3.19
steps:
- name: 在虚拟机上打印node版本号
run: node -v
- name: 在docker上打印hello word
uses: docker://node:18-alpine3.19
with: # 用with覆盖Docker映像的入口点
entrypoint: /bin/echo # Docker的入口点 echo的路径
args: hello word # 你可以认为这是一个参数数组 [''] 参数作为字符串输入
- name: 在docker上打印node版本号
uses: docker://node:18-alpine3.19
with: # 用with覆盖Docker映像的入口点
entrypoint: /usr/local/bin/node # Docker的入口点 node的路径
args: -v # node后面的命令参数 -v
- uses: actions/checkout@v1 # 因为下面要使用存储库的文件,所以加这个
- name: 在docker上运行一个script.sh脚本
uses: docker://node:18-alpine3.19
with: # 用with覆盖Docker映像的入口点
entrypoint: ./script.sh # 在根文件夹中的文件
args: "Some String" # node后面的命令参数 -v

具体的使用方法请参考官方教程的示例

实例:React 项目发布到 GitHub Pages

下面是一个实例,通过 GitHub Actions 构建一个 React 项目,并发布到 GitHub Pages。最终代码都在这个仓库里面,发布后的参考网址为 ruanyf.github.io/github-actions-demo
这个示例需要将构建成果发到 GitHub 仓库,因此需要 GitHub 密钥。按照官方文档,生成一个密钥。然后,将这个密钥储存到当前仓库的 Settings/Secrets 里面。
环境变量的名字可以随便起,这里用的是 ACCESS_TOKEN。如果你不用这个名字,后面脚本里的变量名也要跟着改。
本地计算机使用 create-react-app,生成一个标准的 React 应用。

$ npx create-react-app github-actions-demo
$ cd github-actions-demo

然后,打开package.json文件,加一个homepage字段,表示该应用发布后的根目录(参见官方文档)。

"homepage": "https://[username].github.io/github-actions-demo",

上面代码中,将 [username] 替换成你的 GitHub 用户名,参见范例
在这个仓库的 .github/workflows目录,生成一个 workflow 文件,名字可以随便取,这个示例是ci.yml 。
我们选用一个别人已经写好的 action:JamesIves/github-pages-deploy-action,它提供了 workflow 的范例文件,直接拷贝过来就行了(查看源码)。

name: GitHub Actions Build and Deploy Demo
on:
push:
branches:
- master # 整个流程在 master 分支发生push事件时触发。
jobs:
build-and-deploy:
runs-on: ubuntu-latest # 只有一个job,运行在虚拟机环境 ubuntu-latest
steps:
- name: Checkout
# 第一步是获取源码,使用的 action 是actions/checkout。
uses: actions/checkout@master

- name: Build and Deploy # 第二步是构建和部署
# 使用的 action 是JamesIves/github-pages-deploy-action。
uses: JamesIves/github-pages-deploy-action@master
# 第二步需要四个环境变量,分别为 GitHub 密钥、发布分支、构建成果所在目录、构建脚本。
# 其中,只有 GitHub 密钥是秘密变量,需要写在双括号里面,其他三个都可以直接写在文件里。
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
BRANCH: gh-pages
FOLDER: build
BUILD_SCRIPT: npm install && npm run build

保存上面的文件后,将整个仓库推送到 GitHub。GitHub 发现了 workflow 文件以后,就会自动运行。你可以在网站上实时查看运行日志,日志默认保存30天。

实例:GitHub Action + Release:打造 Electron 持续交付系统