ブログ・ア・ラ・クレーム

技術的なメモとかライフログとか。

GitHub Actions と GitHub Packages を使って、継続的に社内向け gem をリリースする

こんにちは、 syu_cream です。普段は Ubie でプロダクト開発エンジニアをやっています。本日は業務で構築した社内向け gem の継続的なリリースを行う仕組みを紹介します。 GitHub Actions & Packages の組み合わせは問題にハマると原因究明が難しいことがあり、また意外にも世の中に情報が乏しいように思えます。この情報がひとつの参考になればと思います。

はじめに

Ubie では、以下記事で紹介しているように新規に開発するサービスを Go か Node.js で、通信プロトコルを GraphQL か gRPC で実装する方針を進めています。

zenn.dev

最近、僕の所属チームでも新規サービスを追加したい状況に直面し、上記記事の基準を加味して NestJS による gRPC サーバを実装することにしました。現在 Ubie では Protocol Buffers による gRPC のサービスやメッセージの定義と付随するコード生成等のエコシステムは中央集権的なリポジトリが担っています。ここで NestJS で使いやすいコードは既に GitHub Packages で社内向け npm パッケージとして利用可能になっていました。しかし今回開発する gRPC サーバに対する想定クライアントとして昔から存在する Ruby on Rails 実装の REST API サーバがあり、他言語に従い Ruby 向けのコード生成も行いたくなりました。

このような経緯があり、本記事タイトルの「GItHub Packages を使って社内向け gem をリリースする」のを、属人性を排除しつつ簡素な構成で行うため GitHub Actions で完結させたい次第になりました。

GitHub Actions から社内向け gem をリリースする

今回まず Protocol Buffers, gRPC のコード生成をする必要があったのですが、ここは Buf を使いあまりハマることなく実現することができました。以下ページを参考に、 Ruby 向けの gRPC/Protocol Buffers プラグインを導入して buf generate するだけです(まだ複雑なことをしていないが故に問題にハマってないだけの可能性は否めないですが)

buf.build

本題の gem のリリースですが、基本的には以下ドキュメントに従うことになります。

docs.github.com

まず gemspec を用意します。(一部置換を含みつつ抜粋)今回 GitHub のリリースタグからバージョンコントロールしたいので、環境変数から注入可能にしています。また allowed_push_host メタデータは gem push する先にを指定しておきます。 lib/**/*.rb には buf generate で生成されたコードが配置されている想定です。

Gem::Specification.new do |spec|
  spec.name          = "..."
  spec.version       = ENV["PACKAGE_VERSION"]
  spec.authors       = ["..."]

  spec.summary       = ""
  spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
  spec.files         = Dir["Gemfile", "lib/**/*.rb"]
  spec.require_paths = ["lib"]

  spec.metadata["allowed_push_host"] = "https://rubygems.pkg.github.com/<namespace>"

  spec.add_runtime_dependency "grpc", "~> 1.x"
end

次に GitHub Actions のワークフローを組みます。実際に作成したものは以下のようなものになります。(一部置換を含みつつ抜粋) gem push をするにあたり Actions に Packages に書き込みを行う権限が必要です。リポジトリの設定 ( Settings > Actions > General > Workflow permissions ) で書き込み権限が付与されているかワークフローの設定で permissions.packages: write が必要になるはずです。 また公式ドキュメントを参考に gem push する際の認証情報を渡します。

  ruby:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Buf
        uses: bufbuild/buf-setup-action@v1
        with:
          github_token: ${{ github.token }}

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '2.7'
          bundler-cache: true

      - name: Generate code
        run: |
          buf generate ..

      - name: "Define PACKAGE_VERSION"
        run: |
          echo "PACKAGE_VERSION=${{ github.event.release.tag_name }}" >> $GITHUB_ENV

      - name: Build
        run: |
          gem build <gem name>.gemspec

      - name: Publish artifact
        run: |
          echo ":github: Bearer ${GITHUB_TOKEN}" >> ~/.gem/credentials
          chmod 600 ~/.gem/credentials
          gem push --key github --host https://rubygems.pkg.github.com/<namespace> <gem name>-${{env.PACKAGE_VERSION}}.gem
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

このワークフローではそのために明示的に PAT 払い出しをする必要がなく、 GITHUB_TOKEN で簡潔に構成できます。 それゆえ GITHUB_TOKEN 起因の認証エラーにハマることがあるかもしれませんが、トークンの渡し方や付与されている権限を確認しながらトラブルシュートしていくことが求められます。

社内向け gem を利用する

gem がリリースされたら、利用する側も作り込んで行きます。これも基本的に公式ドキュメントにあるように、 Gemfile にリリースされた gem への依存を書いて

source "https://rubygems.pkg.github.com/<namespace>" do
  gem "<gem name>", "<version>"
end

bundle config で source にアクセスする時の認証情報をセットしておきます。これで bundle install でリリースされた社内向け gem をインストールできます。

$ bundle config https://rubygems.pkg.github.com/<namespace> <GitHub のユーザ名>:<GitHub の PAT>

このやり方は GitHub Actions 上で行いたい時障壁になるかもしれません。その場合は環境変数を介して渡すことも可能なようです。

    - uses: ruby/setup-ruby@v1
      with:
        ruby-version: ${{ env.RUBY_VERSION }}
        bundler-cache: true
      env:
        BUNDLE_RUBYGEMS__PKG__GITHUB__COM: ${{ secrets.GITHUB_TOKEN }}

おわりに

というわけで社内向け gem を GitHub Actions & Packages でリリース・利用する事例について紹介しました。 GitHub Actions、 Packages は上手く使えれば簡潔で使いやすい開発基盤を構築できると思うのですが、問題にハマるとストレスフルな状況になりがちかなと思います。本記事がひとつの事例としてお役に立てば幸いです。