使用 GitHub Actions 实现 Go 包 CI/CD
持续集成
发布自动化
把所有东西整合起来
在之前的文章中,我介绍了如何为我的随机站立程序的 Python 版本实现 CI/CD 检查和自动发布。我也为 Go 版本开发了一些类似的流程,所以我想再写一篇关于如何使用 GitHub Actions 打包 CI/CD 的 Go 版本文章。如果你读过之前的文章,这篇文章的内容可能会让你觉得似曾相识——正如我在比较该程序的 Go 和 Python 版本时所描述的那样,我的 CI/CD 目标是一样的:PR 检查和自动发布。
正如我之前所说,我想确保:
- 我对程序所做的每一次更改都不会破坏现有功能(持续集成),而且
- 将新版本发布到pkg.go.dev是自动的(持续交付/部署)。
GitHub 提供了一项名为GitHub Actions 的工作流自动化功能。简而言之,您可以将工作流配置编写在 YAML 文件中your-repo/.github/workflows/,这些配置将在特定仓库事件发生时执行。
持续集成
这个自动化流程相对简单。我希望在每次提交到主干仓库时以及每次拉取请求提交到主干仓库时,都运行以下工作流程:
- 使用 linting 检查来测试语法
golangci-lint——它是 Go 语言最好的 linting 工具(实际上,我认为它是一个元 linting 工具,因为它调用了几个独立的 linting 工具),如果你陷入一些众所周知的反模式,它会提醒你。 - 通过对整个程序运行自动化单元测试来测试其功能。这是一个极其简单的程序,所以我将其设计得非常复杂,将其拆分成多个函数,以便于进行单元测试。
- 通过尝试在 Go 支持的尽可能多的操作系统和架构组合上构建程序(但丢弃构建产物)来测试构建稳定性。当然,我并不指望有人会在 ARM 芯片上使用 Plan 9 运行我的站立式随机数生成器,但这更多的是一次学习 Go 的交叉编译能力的练习。
以下是完整的工作流程。
每次提交到主干。
此触发器在工作流文件的顶部声明:
on:
push:
branches: [main]
pull_request:
branches: [main]
通过检查格式来测试语法
首先,我们需要使用 GitHub Actions自带的checkout操作检出代码仓库。然后,我们需要使用GitHub Actions 的setup-go相应操作设置 Go 版本。GitHub Actions 的运行器支持 3 种不同的操作系统,每种操作系统都支持不同的Go 版本,但最稳妥的做法是明确指定要使用的 Go 版本。
最后,我们可以使用golangci-lint 提供的 GitHub Action进行代码检查——它会golangci-lint在工作流运行器克隆的仓库上运行,如果仓库中的任何 Go 文件不符合任何代码检查器的规则,则会输出错误代码golangci-lint。请注意,golangci-lint如果抽象语法树 (AST)go test无法解析(即存在任何语法错误),则会失败,因此它也可以用于检查语法正确性,而语法正确性本身就是检查合并冲突字符串的一个很好的替代方法。这样,我们就可以快速失败任何检查——如果存在语法错误,则无需启动编译和调用。
jobs:
lint:
name: Lint files
runs-on: 'ubuntu-latest'
steps:
- uses: actions/checkout@v2.3.4
- uses: actions/setup-go@v2
with:
go-version: '1.16.4'
- name: golangci-lint
uses: golangci/golangci-lint-action@v2.5.2
with:
version: latest
测试功能
同样,我们需要检出此作业的仓库并设置 Go 版本:
name: Run tests
runs-on: 'ubuntu-latest'
needs: lint
steps:
- uses: actions/checkout@v2.3.4
- uses: actions/setup-go@v2
with:
go-version: '1.16.4'
- run: go test -v -cover
请注意,与 Python 不同,无需设置即可安装依赖项(go test会自动获取在 中定义的依赖项go.mod)或设置虚拟环境,因此 CI/CD 中的样板代码要少得多。
测试不同操作系统和架构的构建稳定性
Go为各种操作系统和架构提供了交叉编译工具。本质上,你可以运行类似这样的命令:
$ GOOS=plan9 GOARCH=arm go build
Go 编译器将构建一个二进制文件,该文件将在 GOOS 和 GOARCH 指定的操作系统GOOS和架构上运行GOARCH。要查看 GOOS 和 GOARCH 选项的完整列表,请运行go tool dist list。
我们希望验证此数据集的构建稳定性,因此我们可以使用 GitHub Actions 为不同的 GOOS 和 GOARCH 选项设置矩阵构建:
build:
runs-on: 'ubuntu-latest'
needs: test
strategy:
matrix:
goosarch:
- 'aix/ppc64'
- 'android/amd64'
- 'android/arm64'
- 'darwin/amd64'
- 'darwin/arm64'
- 'dragonfly/amd64'
# ...
这是在jobs.<job_id>.strategy.matrix指令中定义的。我为每个 GOOS 和 GOARCH 组合添加了一个变量(为了这篇博文,这里只列出了部分组合——我的工作流文件中定义了 39 个组合)。
从内部来看,这些步骤大致如下:
- GitHub Actions 解析作业指令,发现其中采用了矩阵策略。
- 它为每个矩阵组合启动一个单独的运行器,并将变量定义
matrix.goosarch为该组合的值。 - 它会在步骤 2 中启动的每个运行器中运行作业步骤。
您可以在此处的GitHub Actions 控制台中查看此矩阵运行的示例(所有goosarch值均显示在左侧边栏中)。这些矩阵选项默认并行运行,因此作业的运行时间取决于速度最慢的矩阵选项。请注意,如果您的存储库是私有的,则每个单独的构建矩阵选项都会消耗 Actions 分钟数,macOS 和 Windows 运行程序的消耗倍数更高(截至 2021 年 5 月,macOS 1 分钟相当于 10 分钟 Actions 额度,Windows 1 分钟相当于 2 分钟 Actions 额度)。
我们进行常规的检出和 Go 版本设置,然后对 Bash 字符串进行一些基本的字符分割,以便我们可以从单个矩阵选项中分别/设置GOOS环境变量:GOARCH
- name: Get OS and arch info
run: |
GOOSARCH=${{matrix.goosarch}}
GOOS=${GOOSARCH%/*}
GOARCH=${GOOSARCH#*/}
BINARY_NAME=${{github.repository}}-$GOOS-$GOARCH
echo "BINARY_NAME=$BINARY_NAME" >> $GITHUB_ENV
echo "GOOS=$GOOS" >> $GITHUB_ENV
echo "GOARCH=$GOARCH" >> $GITHUB_ENV
然后,我们只需运行 Go 的go build子命令,即可创建二进制文件:
- name: Build
run: |
go build -o "$BINARY_NAME" -v
自动合并
如果配置了分支保护规则,并且拉取请求通过了所有必需的审查和状态检查,GitHub 也允许自动合并拉取请求。在仓库设置 > 分支 > 分支保护规则中,我定义了一条规则,要求工作流main中的所有任务都build.yml必须通过,分支才能合并到主分支main。
发布自动化
GitHub 发布自动化分为两个部分:
创建 GitHub 发布
我们设置了工作流,使其在推送以“:”开头的标签时触发v。
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
然后,我们定义我们的release任务,它在 Ubuntu 上运行(Ubuntu 是最便宜、速度最快的 GitHub Actions 运行环境):
name: Create Release
jobs:
autorelease:
name: Create Release
runs-on: 'ubuntu-latest'
我还设置了与之前相同的GOOS 和 GOARCH 构建矩阵build.yml- 当我们创建 GitHub 版本时,我们将构建二进制文件并将其作为发布资产上传。
前两步几乎与推送和 PR 的构建工作流程相同main:我们检出仓库并配置 Go。不过,我们的检出步骤略有不同:我们提供了0输入fetch-depth参数,因此会创建一个包含所有提交的深克隆,而不是仅包含最新提交的浅克隆。
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 0
Go 使用版本控制标签来指定模块版本,因此我们不需要像 Python 那样解析任何清单文件。所以,我们可以像以前一样进行 Bash 字符串分割并构建二进制文件:
- name: Get OS and arch info
run: |
GOOSARCH=${{matrix.goosarch}}
GOOS=${GOOSARCH%/*}
GOARCH=${GOOSARCH#*/}
BINARY_NAME=${{github.repository}}-$GOOS-$GOARCH
echo "BINARY_NAME=$BINARY_NAME" >> $GITHUB_ENV
echo "GOOS=$GOOS" >> $GITHUB_ENV
echo "GOARCH=$GOARCH" >> $GITHUB_ENV
- name: Build
run: |
go build -o "$BINARY_NAME" -v
下一步是创建发布说明。我在.github文件夹中保留了一个发布模板,并将一些 gitlog 输出附加到其中:
- name: Release Notes
run: git log $(git describe HEAD~ --tags --abbrev=0)..HEAD --pretty='format:* %h %s%n * %an <%ae>' --no-merges >> ".github/RELEASE-TEMPLATE.md"
那个复杂的 gitlog 命令会检查自上次更新 HEAD 以来的所有提交。对于每个提交,它会将提交哈希值、提交信息主题、作者姓名和作者邮箱地址添加到发布模板中。
最后,我们使用第三方发布创建操作,将我们刚刚创建的发布说明和工件添加到发布草稿中:
- name: Release with Notes
uses: softprops/action-gh-release@v1
with:
body_path: ".github/RELEASE-TEMPLATE.md"
draft: true
files: ${{env.BINARY_NAME}}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
这将创建一个草稿,可在https://github.com/jidicula/random-standup/releases查看。我会根据需要修改发布公告,然后发布版本。
发布到 pkg.go.dev
发布流程的最后一步是通知 pkg.go.dev 模块有新版本可用。以下是完整的流程。
这次,我们触发工作流在发布版本时运行(上一个工作流的最后一步是手动发布版本草稿):
on:
release:
types:
- published
我们按照之前的步骤进行结账。然后,我们只需访问curl模块获取所在的 URL go get:
jobs:
bump-index:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2.3.4
- name: Ping endpoint
run: curl "https://proxy.golang.org/github.com/jidicula/random-standup/@v/$(git describe HEAD --tags --abbrev=0).info"
pkg.go.dev 推荐使用此方法向其索引添加新模块(或模块版本)。
把所有东西整合起来
总的来说,参与这个项目将涉及以下几个方面:
- 请为我的修改提交一个 PR。
- 确认自动合并。
- 重复步骤 1 和 2,直到我准备好释放为止。
main创建指向版本号提升提交的标签。- 将标签推送到 GitHub。
- 等待创建版本运行完成。
- 前往https://github.com/jidicula/random-standup/releases并修改刚刚创建的发布草案的公告。
- 发布新闻稿。
- 等待发布过程完成。
- 请检查pkg.go.dev获取更新后的软件包版本。
如果您有任何问题或意见,请发送电子邮件至johanan+blog@forcepush.tech,或在 Twitter 上找到我@jidiculous,或在下方发表评论。
你觉得这篇文章有用吗?请我喝杯饮料或赞助我吧!
文章来源:https://dev.to/jidicula/go-package-ci-cd-with-github-actions-350o