[メモ]GitHub Actionsで記載したタグ指定から実行するハッシュを確認する

自分で持ってるリポジトリのGitHub Actionsでアクションを指定する場合、v1のようなタグ指定からコミットハッシュに変えようとしたときの備忘録。
（以下のコードのようなことをしたかった）

```diff_yaml
runs:
  using: "composite"
  steps:
-   - uses: ruby/setup-ruby@v1.244.0
+   - uses: ruby/setup-ruby@13e7a03dc3ac6c3798f4570bfead2aed4d96abfb # v1.244.0
      with:
        bundler-cache: true
```

GitHubのページを見に行ってもいいけど、コードで取得するためにはどのようにするといいかな？となりRubyで実装してみることに。

## 実際に作成してみる

- githubを扱う場合の定番ということでoctokit、あとAPIトークンを使いたいので、dotenvをインストール
    - faraday-retryもインストールしておくと警告が出なくて楽です

```sh
gem install octokit dotenv fataday-retry
```

## GitHubのアクセストークンを作製する(人によってはやらなくてもよい)

- 自作のプライベートリポジトリにあるアクションのデータを取得する場合などはアクセストークンが必要です
    - どの権限が必要かはケースによるので、適切なものを選択してください
- 実行回数が少ないかつ、実行対象がパブリックリポジトリの場合はなくても大丈夫です
- アクセストークンを作成したら、`.env`に保存しましょう

```.env
GITHUB_TOKEN=#(取得したトークンをペースト)
```


## 実際にプログラムを作成する

- 以下のRubyプログラムを作成します
    - 今回は`ruby main.rb ruby/setup-ruby@v1.244.0`のような入力をコンソールから入れることを想定します
- まずは最初の部分を見ます
    - 以下を行っています
        - ライブラリのロードと変数読み込み
        - githubのclient作成
        - 標準入力からリポジトリ情報を整理
            - オーナー
            - リポジトリ
            - タグ
    - clientはすべてのタグを表示するために、`auto_paginate`オプションをtrueにします

```ruby
# frozen_string_literal: true

require 'octokit'
require 'dotenv'

Dotenv.load('.env')

GITHUB_TOKEN = ENV['GITHUB_TOKEN']

def client
  @client ||= Octokit::Client.new(access_token: GITHUB_TOKEN, auto_paginate: true)
end

input = ARGV[0]
owner, repo_and_tag = input.split('/', 2)
repo, input_tag = repo_and_tag.split('@', 2)
```

- 次にバージョンが存在するか確認します
    - 完全一致するタグを探して、一致するタグがあった場合それを表示してループを終了します
    - もし見つからない場合は部分一致を探して、一致するタグを表示してループを終了します
    - 部分一致でも見つらない場合は見つからなかったというメッセージを出します

```ruby
tags = client.tags("#{owner}/#{repo}")

found_match = false

tags.each do |tag|
  next unless next unless tag.name == input_tag

  puts "Found tag: #{tag.name} with commit SHA: #{tag.commit.sha}"
  found_match = true
  break
end

unless found_match
  tags.each do |tag|
    next unless array_starts_with?(tag.name.split('.'), input_tag.split('.'))

    puts "Maybe found commit SHA: #{tag.commit.sha} with tag: #{tag.name}"
    found_match = true
    break
  end
end

puts "Tag or commit #{input_tag} not found." unless found_match
```

- エラー処理などをまとめると以下のようになります(長いので折り畳み)

<details><summary>プログラム全部</summary>

```ruby
# frozen_string_literal: true

require 'octokit'
require 'dotenv'

Dotenv.load('.env')

GITHUB_TOKEN = ENV['GITHUB_TOKEN']

def client
  @client ||= Octokit::Client.new(access_token: GITHUB_TOKEN, auto_paginate: true)
end

# input example: ruby/setup-ruby@v1.244.0 and ruby/setup-ruby@v1

input = ARGV[0]
owner, repo_and_tag = input.split('/', 2)
repo, input_tag = repo_and_tag.split('@', 2)

def array_starts_with?(main_array, sub_array)
  return false if sub_array.length > main_array.length

  main_array.take(sub_array.length) == sub_array
end


begin
  tags = client.tags("#{owner}/#{repo}")

  if tags.empty?
    puts 'No tags found.'
    exit
  end

  found_match = false

  tags.each do |tag|
    next unless tag.name == input_tag

    puts "Found tag: #{tag.name} with commit SHA: #{tag.commit.sha}" if tag.name == input_tag
    found_match = true
    break
  end

  unless found_match
    tags.each do |tag|
      next unless array_starts_with?(tag.name.split('.'), input_tag.split('.'))

      puts "Maybe found commit SHA: #{tag.commit.sha} with tag: #{tag.name}"
      found_match = true
      break
    end
  end

  puts "Tag or commit #{input_tag} not found." unless found_match
rescue Octokit::NotFound
  puts "Repository #{owner}/#{repo} not found."
rescue Octokit::TooManyRequests
  puts 'Rate limit exceeded. Please wait or use a Personal Access Token.'
rescue StandardError => e
  puts "An error occurred: #{e.message}"
end
```

</details>


- 実際に実行すると以下のようになります

```sh
Products\hash-test> ruby main.rb ruby/setup-ruby@v1.244.0
Found tag: v1.244.0 with commit SHA: 13e7a03dc3ac6c3798f4570bfead2aed4d96abfb
Products\hash-test> ruby main.rb ruby/setup-ruby@v1
Maybe found commit SHA: 13e7a03dc3ac6c3798f4570bfead2aed4d96abfb with tag: v1.244.0
```

## まとめ
GitHubのAPIは詳細を全部把握するのは大変ですが、octokitを使うと最小限の労力で実装することができます。
`tag.name`を`tag.commit.sha`に変えて若干書き換えると逆にコミットからバージョンを確認できたり、結構色んなことができるので、ぜひ何かGitHubの作業を自動化したいときはoctokitで実装してみてください。