🦑

Github Actions と GoReleaser で Go 製 CLI の CI/CD を実装

2022/09/16に公開

課題

Fargate タイプで起動しているコンテナへ接続する CLI を作成したのですが、機能追加時に手動でリリース作業を行っており、更新するのがだんだん億劫になってきそうなので、そろそろ何かしらで CI/CD できればと思いました。
https://zenn.dev/gaji/articles/a98b74c5ddd5a8

解決策

普段は CircleCI を利用していますが、Github Actions と、homebrew(tap)へのリリースが簡単に行える GoReleaser を利用しようと思います。

自分要件

  1. feature ブランチへの push, main ブランチへのプルリクエスト作成時にテストを実行
  2. リリースタグ作成時にリリース物を自動で添付する

実装

Github Actions

Github Actions でテスト実行については、公式ドキュメントを確認しながら割とスムーズに設定ファイルを作成できました。

test.yml
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 は以下の通りです。

release.yml
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 実行時に利用可能な一時的に払い出されるトークンとなりますが、自リポジトリ以外の権限は弱めです。
https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token

GoReleaser では tap 用のリポジトリへ自動で作成した formula ファイルの push を行うため、tap 用のリポジトリを更新可能なPersonal access tokensを生成しておき、該当の Workflow を実行するリポジトリに HOMEBREW_TAP_GITHUB_TOKEN という名称で secrests を登録し、生成したPersonal access tokensの値を設定します。

GoReleaser

前述の通り goreleaser.yml に各種設定を記載します。

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 を追加する事に気がつかず、ちょっとハマりました。

リリースタグ作成時に以下のように、リリースタグにファイルが添付されました。

今回修正したコードは以下に上げています。
https://github.com/gajirou/fexec

まとめ

CI/CD 実装前は手動でのビルド、リリースタグ作成、タグへのファイル添付、formula ファイルのハッシュ生成を行っていたので GoReleaser のおかげでかなり楽になりました。

また、クロスコンパイルでのモジュール作成も設定ファイルに設定を記載するだけで対応できるので、手動リリース時に検討していた Linux 対応についても簡単に実装できました。

これでモチベーションが上がりそうなので、引き続き機能追加をしていこうと思います。

Discussion