Github Actions と GoReleaser で Go 製 CLI の CI/CD を実装
課題
Fargate タイプで起動しているコンテナへ接続する CLI を作成したのですが、機能追加時に手動でリリース作業を行っており、更新するのがだんだん億劫になってきそうなので、そろそろ何かしらで CI/CD できればと思いました。
解決策
普段は CircleCI を利用していますが、Github Actions と、homebrew(tap)へのリリースが簡単に行える GoReleaser を利用しようと思います。
自分要件
- feature ブランチへの push, main ブランチへのプルリクエスト作成時にテストを実行
- リリースタグ作成時にリリース物を自動で添付する
実装
Github Actions
Github Actions でテスト実行については、公式ドキュメントを確認しながら割とスムーズに設定ファイルを作成できました。
name: fexec-test
on:
# feature ブランチ push 時に実行
push:
branches:
- 'feature/*'
# main ブランチへのプルリクエスト作成時に実行
pull_request:
branches:
- main
jobs:
Test:
runs-on: ubuntu-latest
steps:
# ソースのチェックアウト
- name: Checkout
uses: actions/checkout@v3
# Go の環境作成
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.19
# テストの実行
- name: Test
run: go test -v ./util
GoReleaser を利用するためには、Github Actions で uses + goreleaser.yml が必要となるため、公式ドキュメントの yml を確認しつつ作成しました、Github Actions は以下の通りです。
name: fexec-release
on:
# タグ生成時に実行
push:
tags:
- "*"
jobs:
Release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.19
# GoReleaser を利用しビルド
- name: Build fexec
uses: goreleaser/goreleaser-action@v3
with:
args: build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# GoReleaser を利用しリリース
- name: Release fexec
uses: goreleaser/goreleaser-action@v3
with:
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
secrets.GITHUB_TOKEN
は、Github の REST API 実行時に利用可能な一時的に払い出されるトークンとなりますが、自リポジトリ以外の権限は弱めです。
GoReleaser では tap 用のリポジトリへ自動で作成した formula ファイルの push を行うため、tap 用のリポジトリを更新可能なPersonal access tokens
を生成しておき、該当の Workflow を実行するリポジトリに HOMEBREW_TAP_GITHUB_TOKEN
という名称で secrests を登録し、生成したPersonal access tokens
の値を設定します。
GoReleaser
前述の通り goreleaser.yml に各種設定を記載します。
# 前処理
before:
# 依存モジュールのインストール
hooks:
- go mod tidy
# ビルド
builds:
# ビルド対象ディレクトリ指定
- dir: cmd
# ビルド時の環境変数指定
env:
- CGO_ENABLED=0
# クロスコンパイルの OS オプション
goos:
- darwin
- linux
# クロスコンパイルのアーキテクチャオプション
goarch:
- amd64
- arm64
# ビルド時のフラグ
ldflags:
- -s -w
# 指定したファイル名のアーカイブファイルがリリースタグに添付される
archives:
- name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
# 上記の name テンプレートの置換文字設定
replacements:
darwin: macos
linux: linux
amd64: amd64
# チェンジログの生成
changelog:
# 変更履歴にコミットメッセージを昇順で記載
sort: asc
# コミットメッセージからマッチした文字列を削除
filters:
exclude:
- "^docs:"
- "^test:"
# リリースタグ作成リポジトリ
release:
github:
owner: gajirou
name: fexec
# homebrew 用リポジトリの設定
brews:
- name: fexec
# formula 配置リポジトリ
tap:
owner: gajirou
name: homebrew-fexec
branch: main
# Github Actions の workflow で設定した環境変数で formula ファイルを push
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
commit_author:
name: gajirou
email: lifelongsre@gmail.com
description: "Connect to a container running on AWS Fargate."
# formula のテスト指定
test: |
system "#{bin}/goreleaser -v"
dependencies:
- name: go
release 時に dist ディレクトリ配下にもろもろのファイルが格納されるのですが、こちらが git の dirty state となるエラーが発生しました、rm --rm-dist
しているのにと思いましたが、gitignore に dist を追加する事に気がつかず、ちょっとハマりました。
リリースタグ作成時に以下のように、リリースタグにファイルが添付されました。
今回修正したコードは以下に上げています。
まとめ
CI/CD 実装前は手動でのビルド、リリースタグ作成、タグへのファイル添付、formula ファイルのハッシュ生成を行っていたので GoReleaser のおかげでかなり楽になりました。
また、クロスコンパイルでのモジュール作成も設定ファイルに設定を記載するだけで対応できるので、手動リリース時に検討していた Linux 対応についても簡単に実装できました。
これでモチベーションが上がりそうなので、引き続き機能追加をしていこうと思います。
Discussion