GitHub Actions から Credentials 管理不要で gem を push できるようになった

去年の12月頃に [Trusted Publishing](https://guides.rubygems.org/trusted-publishing/) という機能が RubyGems.org に入った。

これにより、↓ の  release-gem Action を利用して、 Credentials なしで GitHub Actions から gem のリリース出来るようになった。
この記事では、これの設定方法を説明する。

https://github.com/rubygems/release-gem 

<details><summary>Trusted Publishing について詳細</summary>

これは 「指定した GitHub リポジトリの指定した Workflow に対して、(指定した) gem の push を認可する」機能。これを設定すると、GitHub Actions で Credentials の登録をすることなく gem を push 出来るようになる。

https://blog.rubygems.org/2023/12/14/trusted-publishing.html

https://github.com/rubygems/rubygems.org/issues/4286

この機能は内部的には、 Open ID Connect (OIDC) を利用していて、これにより short-lived な access token を都度取得出来る。

https://docs.github.com/ja/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#updating-your-workflows-for-oidc

この方法だと通常は ↑ の記事にあるようなトークンの交換処理を行う必要があるのだが、以下の Custom Action を使うことで、簡単にトークンの交換や gem の release が行える。

https://github.com/rubygems/release-gem

https://github.com/rubygems/configure-rubygems-credentials

</details>

## 1. RubyGems.org 側で Trusted Publishing の設定を行う

(※ https://guides.rubygems.org/trusted-publishing/adding-a-publisher/ の内容とほぼ同じ)

(RubyGems.org にログインした状態で、) 設定を行いたい Gem のページの Link から、 `信頼できる発行元` (または `Trusted Publishers`) を選択し、遷移先の画面から `作成` (または `Create`) を選ぶ。

![スクリーンショット 2024-12-23 13.40.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/9d9ae021-cf48-ba6c-da41-c2375bfdd7b0.png)

![スクリーンショット 2024-12-23 13.41.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/7a8bf7a3-b93d-b90e-926a-5fc522952312.png)

すると、登録フォームに遷移する。

![スクリーンショット 2024-12-23 13.41.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/194a482a-c752-8f2d-5731-d233166b73e4.png)

このフォームで、

- 認可するリポジトリ (Repository Owner / Repository Name の項目)
- 認可する GitHub Workflow のファイル名
- 認可する [Environment](https://docs.github.com/ja/actions/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment) 名

を入力し、`Rubygem Trusted publisher を作成` (または `Create Rubygem Trusted publisher`) で登録完了。

Environment は任意だが、実行ブランチの制限や実行時のレビュー必須化など、保護機能が追加で備わっているのでおすすめ。

https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment

(新規の gem に対しても似たような手順で設定が行える。詳しくは https://guides.rubygems.org/trusted-publishing/pushing-a-new-gem/ を参照。)

## 2.  release-gem Action を使って Workflow を実装する

(※ https://guides.rubygems.org/trusted-publishing/releasing-gems/ の内容がベース)

https://github.com/marketplace/actions/release-gem を使った、こういう感じの Workflow を実装する。

```yaml
# ファイル名は、 1. で登録したファイル名にする
name: Release Gem on RubyGems.org

on:
  # push でも workflow_dispatch でもどっちでも OK
  push:
    tags:
      - v*
  workflow_dispatch:

jobs:
  push:
    runs-on: ubuntu-latest
    # 1. で登録した Environment 名にする必要がある
    environment: release

    permissions:
      contents: write
      id-token: write

    steps:
      - uses: actions/checkout@v4

      # `gem exec` が使えるバージョンの rubygems gem が必要。(後述)
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
          ruby-version: ruby
          # rubygems: latest

      - uses: rubygems/release-gem@v1
```

<details><summary>何やってるか解説</summary>

release-gem Action が行っていることはざっくり以下の通り。

1. OIDC を利用し、RubyGems.org の short-lived な access token を取得
    - (内部的には [Configure RubyGems Credentials](https://github.com/marketplace/actions/configure-rubygems-credentials) Action を利用)
1. `rake release` を実行して gem のリリースを行う
    - この際に [rubygems gem](https://github.com/rubygems/release-gem/blob/v1/rubygems-attestation-patch.rb) に patch を当てていて、可能なら署名を行いつつ gem を push する

この際に実行している `rake release` というのは、Bundler が提供している gem の release 用のタスクで、

* version tag を打って git push
* rubygems への gem の push

を行ってくれる。

(Rakefile 内で `require "bundler/gem_tasks"` とすると使える。 `bundle gem` とかで gem を作っていると最初から入っている。)

https://github.com/rubygems/rubygems/blob/v3.6.1/bundler/lib/bundler/gem_helper.rb#L65-L69

</details>



## ハマりどころとその解消法
### ※ rubygems gem のバージョンが古いと `gem exec` が実行できずエラーになる

とまあ、便利な [release-gem](https://github.com/rubygems/release-gem) なのだが、 Ruby 3.1.x など、古めの version で動かすと、以下のようなエラーになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7048/55d0c972-5fd3-ceca-3c9a-fa399dd66b21.png)

```
ERROR:  While executing gem ... (Gem::UnknownCommandError)
    Unknown command exec
	/opt/hostedtoolcache/Ruby/3.1.6/x64/lib/ruby/3.1.0/rubygems/command_manager.rb:198:in `find_command'
	/opt/hostedtoolcache/Ruby/3.1.6/x64/lib/ruby/3.1.0/rubygems/command_manager.rb:183:in `process_args'
	/opt/hostedtoolcache/Ruby/3.1.6/x64/lib/ruby/3.1.0/rubygems/command_manager.rb:149:in `run'
	/opt/hostedtoolcache/Ruby/3.1.6/x64/lib/ruby/3.1.0/rubygems/gem_runner.rb:51:in `run'
	/opt/hostedtoolcache/Ruby/3.1.6/x64/bin/gem:21:in `<main>'
```

これは release-gem が `gem exec` (npx, npm exec の ruby 版) という機能を使っていて、 `gem exec` が入ったのが [rubygems gem 3.4.8](https://github.com/rubygems/rubygems/blob/master/CHANGELOG.md#348--2023-03-08) というそこそこ新し目のバージョンのため。

このエラーを回避するには、 setup-ruby 側で `rubygems: 3.4.8` を指定するか、rubygems gem のデフォルトバージョンが新しいもの (3.2.6 以降など) を指定すると良い。

https://github.com/rubygems/release-gem/issues/1#issuecomment-1858826961

https://stdgems.org/rubygems/