iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

GitHub Actionsでリリースミスを防ぐ!自動化ワークフローの超初級解説

こんにちは!株式会社iimonでエンジニアをしている「まるお」です。

先日、拡張機能アプリの最新版をリリースする際に、manifest.jsonのバージョンを更新する前にタグを誤って先にプッシュしてしまいました。こうしたミスを防ぐために、リリース作業を少しずつ自動化していけたらと思い、GitHub Actionsのワークフローの書き方を一から学びました。

今回の最終的な目標は、 リモートリポジトリのmanifest.jsonとタグのバージョンを比較して、異なる場合はタグをプッシュできないようにするワークフローを作成する ことです! GitHub Actionsを初めて触る方にお役に立つ記事になれば嬉しいです。

GitHub Actionsとは何か?

GitHub Actionsは、コードを更新した際に、自動でテストを実行したり、ビルドやデプロイを行うことができる機能です。.github/workflowsディレクトリ内にymlファイルを作成し、ワークフローをファイル内に書きます。トリガーとなるイベント(例: push, pull_request)が発生すると、ワークフローが実行されます。

ワークフローとは?

GitHub Actionsのワークフローは、1 つ以上のジョブを実行する構成可能な自動化プロセスです。 たとえば「コードをプッシュしたらテストを行う」といった操作を自動化できます。 手動でトリガーしたり、定義されたスケジュールでトリガーしたりすることもできます。 1 つのリポジトリで複数のワークフローが使用可能。

ワークフローの書き方を学ぶ

ワークフロー例

下記のワークフローを使って書き方を学んでいきたいと思います。

.github/workflows/publish.yml

name: 'Publish'
on:
    workflow_dispatch:
    push:
        tags:
            - 'v*.*.*'

permissions:
    id-token: write
    contents: write
    actions: read

jobs:
    releaseAndUpload:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v3
            - name: Get app version name
              id: get_tag_version
              run: echo "TAG_VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_OUTPUT
            - name: Get manifest version
              id: get_app_version
              run: echo "APP_VERSION=$(jq -r .version manifest/manifest.json)" >> $GITHUB_OUTPUT
            - name: Version check
              run: |
                  echo "tag version: ${{ steps.get_tag_version.outputs.TAG_VERSION }}"
                  echo "manifest version: ${{ steps.get_app_version.outputs.APP_VERSION }}"
                  echo 'manifest.jsonのバージョンとタグのバージョンが異なります'
                  exit 1
              if: ${{!contains(steps.get_tag_version.outputs.tag_VERSION , steps.get_app_version.outputs.APP_VERSION)}}

解説

  • name: ワークフローの名前

    • 今回はPublish
    • GitHub では、ワークフローの名前がリポジトリの [アクション] タブに表示されます。
      name: 'Publish'
    
  • on: イベントトリガー

    • 手動でトリガー可能
    • v*.*.* の形式に一致するタグのプッシュでトリガー
      on:
          workflow_dispatch:
          push:
              tags:
                  - 'v*.*.*'
    
    • その他
    イベント 説明
    push リモートリポジトリへpush時
    pull_request プルリクエスト作成時
    deployment デプロイ時
    release リリース時
    issues GitHub Issues関連の処理発生時
    schedule cronによる定期実行
    workflow_dispatch 手動で実行
  • permissions: ワークフロー内での権限設定

    • 最上位のキーとして、ワークフロー内のすべてのジョブに適用するか、 特定のジョブ内に permissions キーを追加するとそのジョブ内で使用可能。
    • 使用可能なアクセス許可ごとに、read、write、none のいずれかのアクセス レベルを割り当てることができる(write には read が含まれる)。
    • 指定されていないすべてのアクセス許可が none に設定。
    • IDトークンの書き込み、読み込み権限、リリース作成(リポジトリの操作が可能)、他のアクションの読み取り権限
      permissions:
          id-token: write
          contents: write
          actions: read
    
  • jobs: 処理の最上位単位(ワークフローはジョブ単位で分けられてる)

    • jobsの中に1つ以上のジョブIDと共に処理が設定され並列で実行
    • releaseAndUploadというジョブが定義
      jobs:
          releaseAndUpload:
    
  • runs-on: ジョブを実行するOS(ランナー)を指定

    • 各ジョブは、runs-on で指定されたランナー環境で実行
    • 最新のUbuntu
      runs-on: ubuntu-latest
    
  • steps: ジョブはさらにステップ単位で分けられており、ステップ内にコマンドなどの処理が実行

      - uses: actions/checkout@v3
    
    • アプリのタグバージョンを取得
      • name: ステップの名前、GitHub Actions の実行ログに表示
      • id: ステップにIDを付けることで、後続のステップでこのステップの出力を参照可能に
      • run: 実行コマンド
        • ここでは、タグ(GitHubが管理する環境変数 GITHUB_REF に含まれる値)からバージョン番号を取得して、環境変数 TAG_VERSION に保存
        • GITHUB_REFには、refs/tags/v1.2.3のようなタグ情報が格納されているので、refs/tags/v を空文字に置き換えて1.2.3のみ抽出して、TAG_VERSION=1.2.3 という情報を $GITHUB_OUTPUT に追加
      - name: Get app version name
        id: get_tag_version
        run: echo "TAG_VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_OUTPUT
    
    • マニフェストのバージョン取得
      • manifest/manifest.json から拡張のバージョン番号を取得し、APP_VERSION に保存
        • jq コマンド:JSONデータを操作するためのコマンドラインツール
        • オプション r:出力を文字列として取得
        • .version:JSONファイルの version フィールドを指定
        • manifest/manifest.json:アプリの情報が記載されている JSON ファイル
      - name: Get manifest version
        id: get_app_version
        run: echo "APP_VERSION=$(jq -r .version manifest/manifest.json)" >> $GITHUB_OUTPUT
    

    manifest/manifest.jsonが次のようなJSONファイルだった場合

      {
        "name": "Iimon Chrome Extension",
        "version": "1.2.3"
      }
    

    jq -r .version を実行すると、”1.2.3” という文字列が取得。

    • バージョン確認
      • タグバージョンとマニフェストバージョンが一致しているか確認
      • if: この条件が true の場合にのみ、このステップが実行
        • contains(A, B) :AにBが含まれているかを判定 steps.get_tag_version.outputs.TAG_VERSION:タグのバージョン steps.get_app_version.outputs.APP_VERSION:マニフェストのバージョン
        • exit 1 : タグのバージョン(TAG_VERSION)とマニフェストバージョン(APP_VERSION)が不一致の場合、エラーメッセージを出力し、ステップを終了。
      - name: Version check
        run: |
            echo "tag version: ${{ steps.get_tag_version.outputs.TAG_VERSION }}"
            echo "manifest version: ${{ steps.get_app_version.outputs.APP_VERSION }}"
            echo 'manifest.jsonのバージョンとタグのバージョンが異なります'
            exit 1
        if: ${{!contains(steps.get_tag_version.outputs.TAG_VERSION , steps.get_app_version.outputs.APP_VERSION)}}
    

ちなみに、、

タグとマニフェストのバージョンは、どのリポジトリを見ているのか?(リモート?ローカル?)
  • タグバージョンは、、

    • このワークフローのトリガーは、'v*.*.*'という形式のタグがリモートリポジトリにプッシュされた場合に起動するため、リモートリポジトリにプッシュされたものを見ています
  • マニフェストのバージョンは、、

    • ステップ actions/checkout@v3 によって リモートリポジトリがローカル環境にクローンされたもの を見ているので、ローカルの manifest.json ファイルを見ています。
    • ローカルのファイルを参照しているわけではないようなので、原因を引き続き調査していきます。
  • よって、

    • 今回私がマニフェストバージョンをpushする前にタグをpushしても、ステップが終了しなかったのは、ローカル上ではマニフェストバージョンを書き換えており(pushし忘れ)、そのバージョンとプッシュしたタグバージョンが一致していたためでした。

今回やりたいこと

リモートの manifest.json とタグのバージョンが一致するか確認

既存の「マニフェスト」バージョン取得のステップの前に、下記処理を追加

  • リモートリポジトリから最新の manifest.json を取得
    • リポジトリの最新状態を取得し、そこから manifest.json のバージョンを読み取る。
 jobs:
        releaseAndUpload:
            runs-on: ubuntu-latest
            steps:
                - uses: actions/checkout@v3
                - name: Get app version name
                  id: get_tag_version
                  run: echo "TAG_VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_OUTPUT
                
                # リモートの manifest.json を取得
                - name: Fetch remote manifest.json
                  run: |
                    git fetch origin main
                    git checkout origin/main -- manifest/manifest.json
                
                - name: Get manifest version
                  id: get_app_version
                  run: echo "APP_VERSION=$(jq -r .version manifest/rent-manifest/manifest.json)" >> $GITHUB_OUTPUT
                - name: Version check
                  run: |
                      echo "tag version: ${{ steps.get_tag_version.outputs.TAG_VERSION }}"
                      echo "manifest version: ${{ steps.get_app_version.outputs.APP_VERSION }}"
                      echo 'manifest.jsonのバージョンとタグのバージョンが異なります'
                      exit 1
                  if: ${{!contains(steps.get_tag_version.outputs.tag_VERSION , steps.get_app_version.outputs.APP_VERSION)}}

コードの解説

1. git fetch origin main

  • リモートリポジトリの main ブランチの最新情報をローカルに取得
  • 現在の作業ブランチには影響与えず

2. git checkout origin/main -- manifest/manifest.json

  • リモートリポジトリの main ブランチの manifest/manifest.json ファイルをローカルの作業ディレクトリにコピー
  • リモートリポジトリのの manifest.json ファイルを直接ローカルで使用できるように

結果

上記追加コードにより、ローカルのマニフェストバージョンをpushする前にタグをpushしてしまったら、途中でステップが中断するようになりました。

まとめ

初めてワークフローを見た時はどれが何をしているか分からなかったのですが、仕組みや書き方をある程度理解すればやっていて楽しいなと感じました。

今回は基本的な書き方しか学んでいないですが、今後さらに書き方を学んで、本番申請など今手作業でやっている業務の部分をgithub Actionsでさらに自動化させたいと思いました!

最後になりますが、現在弊社ではエンジニアを募集しています!

この記事を読んで少しでも興味を持ってくださった方は、ぜひカジュアル面談でお話ししましょう!

iimon採用サイト / Wantedly / Green

最後まで読んでいただきありがとうございました!!!

参考

https://docs.github.com/ja/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idname

https://qiita.com/s3i7h/items/b50ceb0008edc3c0312e#%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E4%BD%9C%E6%88%90%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E8%BF%BD%E5%8A%A0

https://qiita.com/Take_nakaji/items/6317886c075efe4b7a46#%E3%83%AF%E3%83%BC%E3%82%AF%E3%83%95%E3%83%AD%E3%83%BC

https://qiita.com/shun198/items/14cdba2d8e58ab96cf95#jobs