【初心者】GitHub Actions のカスタムアクションを作ってみる

# はじめに
こんにちは、BTMの藤野です。
GitHub Actions を勉強していてカスタムアクションはどうやったら作れるのかな？と思ったので作りかたを調べて纏めてみました。
### やりたいこと
やりたいこととしては下記の2点になりますので、作成するカスタムアクションは単純に「Hello, World!」を出力する程度のものになります

1. GitHub Actions で使えるカスタムアクションを作成したい
1. 作成したカスタムアクションを同じユーザーの各リポジトリから呼び出し実行したい

# GitHub Actions のカスタムアクションとは？
GitHub Actions のワークフロー内で再利用可能な独自のアクションを作成し、特定の処理の実行を提供できる仕組みです。複数のワークフローから同じ処理を再利用したりできます。

### カスタムアクションの種類
カスタムアクションには、Docker コンテナアクション、JavaScript アクション、Composite アクションの3種類があります。

#### Docker コンテナアクション
- Dockerfile を作成しコンテナ内でアクションを実行させる方法です

#### JavaScriptアクション
- JavaScript を作成しアクションを実行する方法です

#### Composite アクション
- 他のアクションを呼び出したり、シェルコマンドをYAMLファイル内で実装しアクションを実行する方法です

<b>※本稿では JavaScript アクションを使ってカスタムアクションを作っていきます。</b>

# カスタムアクションを作ってみる

それでは早速 `Hello World!` を出力するカスタムアクションを作っていきます。

### 事前準備
#### Personal Access Token の取得
[こちらを参考](https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#personal-access-token-classic-%E3%81%AE%E4%BD%9C%E6%88%90) に GitHub にアクセスし Personal Access Token を取得します
- この際 Select scope で `workflow` にチェックを付ける必要があります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824566/6b3e536f-b91e-7e59-f37a-2a0d2aea1425.png)

#### 必要なディレクトリ／ファイルの作成
1. アクションをホストするリポジトリとして、自身の GitHub アカウント上に適当なプライベートリポジトリ（今回は `my_custom_actions_repo`）を作成します
1. 作成したリポジトリをローカルに `git clone` します
    - ユーザー名とパスワードを聞かれると思うので、GitHub のユーザー名と先ほど作成した Personal Access Token 指定する（[参考](https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89-%E3%83%A9%E3%82%A4%E3%83%B3%E3%81%A7-personal-access-token-%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B)）
1. Clone したリポジトリで以下の構成で各ファイル作成します
    ```
    my_custom_actions_repo
    ├── .gituhb
    │   └── workflows
    │       └── hello-world.yml
    ├── action.yml
    └── index.js
    ```
※ 後述の<b>@vercel/ncc でコンパイル</b>で `npm` を使うので `Node.js` のインストールしておく必要があります。

### ファイルごとの記述と解説

#### ワークフローファイルの定義
- GitHub Actions を実行するための処理を記述したワークフローファイルです
- ワークフローファイルは `.github/workflows/` 配下に YAML ファイルとして配置します
    - ファイル名は何でも良いですが、`.github/workflows/` のディレクトリ構造が必要です
- このワークフローファイルでは同リポジトリ内のアクションを呼び出す処理を記述しています
    ```.github/workflows/hello-world.yml
    name: Hello World Workflow             # ワークフローの名前
    
    on:                                    # このワークフローが実行される条件を指定
      push:                                # この場合 main ブランチにプッシュされた際にワークフローを実行する
        branches:
          - main 
    
    jobs:                                  # ワークフローで実行するジョブを定義する
      hello-world:                         # ジョブのIDを指定する
        runs-on: ubuntu-latest             # ジョブが実行される環境を指定。この場合最新のUbuntuが使用される。
        steps:                             # ジョブで実行する処理を定義する
          - name: Checkout code            # ステップの名前
            uses: actions/checkout@v4      # アクションを呼び出す。
            　                             # actions/checkout@v4 とはGitHub Actions の公式アクションで、リポジトリのコードをチェックアウトする
    
          - name: Run Hello World Action
            uses: ./                       # リポジトリ内のアクションを実行する（ ./ とすることで、同階層の action.yml を読み込む）
    ```

#### アクションファイルと JavaScript ファイルの定義
- カスタムアクションの為の定義を `action.yml` として定義します
    - ファイル名は `action.yml` または `action.yaml` である必要があります
- ここでは、入力値として name を取得し、index.js を呼び出す処理を記述しています
    ```action.yml
    name: "Hello World Action"             # アクションの名前を指定する
    description: "Outputs Hello, World!"   # アクションの説明を記述する
    inputs:                                # アクションが受け取る入力値（引数のようなもの）を定義する
      name:                                # name という入力名とする
        description: "Your name"           # 入力値の簡単な説明
        required: false                    # 必須かどうか（この場合は必須ではない）
        default: "World"                   # 入力値が無い場合のデフォルト値
    runs:                                  # アクションの実行方法を定義する
      using: "node20"                      # アクションが実行する環境（この場合は node20）
      main: "index.js"                     # アクションの実行時に読み込まれるスクリプトを指定
    ```

- アクションファイルから呼び出す JavaScript ファイルを作成します
- ここでは、アクションファイルからの入力値を取得して「Hello [name]!」を出力する処理を記述しています
- @action/core については後述します
    ```index.js
    import { getInput, info, setFailed } from '@actions/core'; // @actions/core から必要な関数を取得する
    
    try {
        const name = getInput('name'); // アクションからの入力値 name を取得する
        info(`Hello, ${name}!`); // 「Hello [name]!」を出力する
    } catch (error) {
        setFailed(error.message); // エラー時の処理
    }
    ```

#### @vercel/ncc でコンパイル
GitHub Actions の JavaScript アクション では、アクションのコードをホストするリポジトリにすべての依存関係を含める必要があるため、ここでは GitHub Actions のドキュメントに沿って、@vercel/ncc を使ってコンパイルします。（[参考](https://docs.github.com/ja/actions/sharing-automations/creating-actions/creating-a-javascript-action#commit-tag-and-push-your-action-to-github)）

1. まず、GitHub Actions で JavaScript アクションを実行する際に必要となるパッケージをインストールします
    ```
    npm install @actions/core
    npm install @actions/github
    ```

1. `@vercel/ncc` をインストールします
    ```
    npm i -g @vercel/ncc
    ```    
1. index.js をコンパイルします
    ```
    ncc build index.js
    ```

1. コンパイルされた js ファイルが `dist/index.js` として出力されるので、`action.yml` の呼び出すスクリプトファイルのパスを修正します

    ```action.yml
    ・・・省略
    runs:
      using: "node20"
      main: "dist/index.js"                     # スクリプトのパスを修正
    ```

#### リポジトリにプッシュ
作成したファイルをリポジトリにプッシュします。

```
git add .
git commit -m "create my_custom_actions_repo"
git push origin main
```
#### Actions の確認
GitHub のリポジトリページの Actions から実行されたワークフローを確認していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824566/6da3c05c-ed73-a77f-cd0e-42a48b630f30.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824566/c58b0f55-4ce7-f792-017d-e474ad79a88a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824566/670e1f50-c6c1-6f19-15f1-d565b60d6941.png)
ちゃんと「Hello, World！」が出力されていることを確認できました！

### タグの追加
アクションのリリースにはバージョンタグを加えることがベストプラクティスとのことなので、タグを追加します。
```
git tag -a -m "release my_custom_actions_repo" v1.0.0
```
# 別リポジトリから呼び出す
上記までで「Hello, [name]!」を表示するアクションは作成できましたので、別リポジトリから呼び出せるようにします。
### 同ユーザーからの Actions のアクセスを許可する
1. 現時点では同ユーザーの別リポジトリへの参照が許可されていないと思うので、リポジトリページから <b>Settings > Actions > General</b> を表示します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824566/3a3434e8-6d4b-b369-e05b-44da2595f4df.png)

1. 一番下の Access というところから `Accessible from repositories owned by the user '<username>'` を選択し保存します
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824566/df126ad7-9c7e-1f8a-8482-04229b16998c.png)

### 別リポジトリのワークフローを作成
別のリポジトリを作成し、作成したカスタムアクションを呼び出すワークフローを実装していきます
1. 自身の GitHub アカウント上に適当なプライベートリポジトリ（今回は `my_actions_repo`）を作成します
1. 作成したリポジトリをローカルに `git clone` します
    - カスタムアクションをホストしたリポジトリの時と同様にスコープに `workflow` が設定された Personal Access Token を指定します
1. Clone したリポジトリで以下の構成でワークフローファイル作成します
    ```
    my_custom_actions_repo
    └── .gituhb
         └── workflows
           └── hello-name.yml
    ```

### ファイルごとの記述と解説

#### ワークフローファイルの定義
- 基本的な記述はカスタムアクション作成時に定義したワークフローと同様ですが、作成したカスタムアクションを呼び出す設定となっています
    ```hello-name.yml
    name: Use Custom Actions
    
    on:
      push:
        branches:
          - main
    
    jobs:
      hello-world:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout code
            uses: actions/checkout@v4
    
          - name: Use custom action
            uses: btm-m-fujino/my_custom_actions_repo@v1.0.0 # 作成したカスタムアクションのリポジトリとタグを指定
            with:                                            # 入力値を指定する
              name: 'm-fujino'                               # 入力値 name に適当な名前を指定する
    ```
#### リポジトリにプッシュ
作成したファイルをリポジトリにプッシュします。
```
git add .
git commit -m "create my_actions_repo"
git push origin main
```

#### Actions の確認
カスタムアクションをホストしたリポジトリでの確認と同様に GitHub のリポジトリページの Actions から実行されたワークフローを確認していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824566/ea0291fc-7612-82c4-0b68-13d0225f01aa.png)

今度は「Hello, [name]！」が出力されていることを確認できました！

# まとめ
- GitHub Actions のカスタムアクションとは GitHub Actions ワークフローから呼び出せる再利用可能な処理を提供できる仕組み
- カスタムアクションは Docker コンテナアクション、JavaScript アクション、Composite アクションの3種類がある
- カスタムアクションをホストするリポジトリでは action.yml を定義する必要がある
- JavaScript アクションの場合は action.yml から呼び出す JavaScript を作成する必要があり、依存関係をすべて含めるため `@vercel/ncc` 等でコンパイルする
- 同ユーザーの別リポジトリから参照するためにはカスタムアクションをホストするリポジトリの Settings ページからアクセス許可を設定する必要がある

# おわりに
カスタムアクションを作って他リポジトリから呼び出せることができました。
実際の案件ではより複雑な処理が必要となると思いますが基本的な使い方は把握できるかなと思いますので、ご参考になれば幸いです。

:::note info
株式会社BTMではエンジニアの採用をしております。
ご興味がある方はぜひ[コチラ](https://www.b-tm.co.jp/recruit/mid-career.html)をご覧ください。
:::