ド素人がパッケージリリースを自動化した話
🌼 はじめに
こんにちは、github acions ド素人です。
最近、社内で自作したUIライブラリの運用に関わることになりました。それは良いことですが、私が参画した時点ではリリースもリリースノートの作成もすべて手作業でした。
そのため、リリースが結構時間かかるかつめんどくさかったので、リリース頻度が減り、生産性が落ちるという課題がありました。
私は github acions については「そういうのがある」ぐらいしか知らなかったですが、リリースがあまりに面倒だったため、なんとか自動化したい!という気持ちでなんとかしました。その話をします。
Runner 設定
1番最初の段階は github acions が動くようにすることでした。
現在のプロジェクトでは、github acions が self-hosted runners でしか動かないため、Runner の設定する必要があったのです。
そこら辺の知識は本当に1ミリもなかったので、インフラに詳しいメンバーに泣きついて一緒に設定してもらいました(その方に無限の感謝を送ります)
self-hosted のプロジェクトそんなないと思いますので、大体の場合はこの段階は不要かと思います。
リリースノート作成自動化
github acions が動けるようになってからまず取り組んだことは、リリースノートの作成の自動化です。
色んなところで紹介されてる release-drafter を使いました。
release-drafter を使うためには Workflow ファイルと設定ファイルの2つのファイルが必要です。
Workflowファイルは README 通りにすれば問題なく動いてたので、設定ファイルだけ状況に合わせてすこしカスタムしました。
name-template: 'v$RESOLVED_VERSION 🌈'
tag-template: 'v$RESOLVED_VERSION'
categories:
- title: '❗️ Breaking Changes'
labels:
- 'major'
- title: '🚀 Features'
labels:
- 'enhancement'
- title: '🐛 Bug Fixes'
labels:
- 'fix'
exclude-labels:
- 'chore'
autolabeler:
- label: 'chore'
branch:
- '/^chore\/.+/'
- '/^docs\/.+/'
- label: 'fix'
branch:
- '/^fix\/.+/'
- label: 'enhancement'
- '/^feature\/.+/'
- '/^feat\/.+/'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
version-resolver:
major:
labels:
- 'major'
minor:
labels:
- 'minor'
patch:
labels:
- 'patch'
default: patch
template: |
## Changes
$CHANGES
重要な部分を解説していきます。
ラベル付与自動化
まずブランチ名に基づいて実装カテゴリラベルが自動で付与されるようにしました。release-drafter は Autolabeler 機能を内蔵しているため、autolabeler
オプションでラベル自動付与ができます。
autolabeler:
- label: 'chore'
branch:
- '/^chore\/.+/'
- '/^docs\/.+/'
- label: 'fix'
branch:
- '/^fix\/.+/'
- label: 'enhancement'
- '/^feature\/.+/'
- '/^feat\/.+/'
-
chore/
もしくはdocs/
で始まるブランチ名のPRにはchore
ラベルが付与される -
fix/
で始まるブランチ名のPRにはfix
ラベルが付与される -
feature/
もしくはfeat/
で始まるブランチ名のPRにはenhancement
ラベルが付与される
カテゴリー分類
categories
オプションを使用すると、ラベルをもとにPRを分類してリリースノートに記載することができます。
categories:
- title: '❗️ Breaking Changes'
labels:
- 'major'
- title: '🚀 Features'
labels:
- 'enhancement'
- title: '🐛 Bug Fixes'
labels:
- 'fix'
-
major
ラベルがついてるPRがマージされたら、リリースノートの❗️ Breaking Changes
の項目として記載される -
enhancement
ラベルがついてるPRがマージされたら、リリースノートの🚀 Features
の項目として記載される -
fix
ラベルがついてるPRがマージされたら、リリースノートの🐛 Bug Fixes
の項目として記載される
リリースノートに含まないPR指定
exclude-labels
オプションを使うと、ラベルをもとにリリースノートからPRを除外することができます。
exclude-labels:
- 'chore'
ymlファイル修正やグリーンキープなど、開発側にだけ関わるPRはリリースノートにのせなくてよいという判断になったので、chore
ラベルはリリースノートから除外しました。
バージョニング指定
version-resolver
オプションを使うと、各PRのラベルに基づいてバージョン番号を自動で上げることができます。
version-resolver:
major:
labels:
- 'major'
minor:
labels:
- 'minor'
patch:
labels:
- 'patch'
default: patch
-
major
ラベル付きのPRがマージされたらメージャーバージョンアップされたリリースノートが作られる -
minor
ラベル付きのPRがマージされたらマイナーバージョンアップされたリリースノートが作られる -
patch
ラベル付きのPRがマージされたらパッチバージョンアップされたリリースノートが作られる - バージョニングラブルがない場合、デフォルトでパッチバージョンアップされたリリースノートが作られる
Draft のリリースノート確認
もろもろ設定が終わったら、PRをマージしてちゃんと動いてるか確認します。
Github の releases ページで以下のように自動で Draft のリリースノートが作成されてるなら設定完了です!
+)この画像は release-drafter の README から持ってきたものなので、この記事で紹介した設定と違う見た目になってます。
image from release-drafter
リリースノート公開したいときは、生成されたドラフトリリースノートの編集ボタンをクリック、Publish ボタン押せば公開完了です。
手で全部いちいち書いてる時に比べたら大分楽になりましたね。
リリースプロセス自動化
リリースノート自動化できたので、リリースプロセスも自動化します。
以前は手でひとつずつコマンドを打ってリリースしてたので、それをWorkflowにやらせました。
リリースのやり方はボタンポチでやることにしたので on.workflow_dispatch
を採用してます。
on:
workflow_dispatch:
権限確認
まず最初の悩みは「管理者権限のメンバーだけリリースできるようにしたい」どいうことでした。
手動リリースでは、認証情報を持ってるのは管理者権限のメンバーだけなのでそういう心配はなかったのですが、gitHub actions を使うと認証情報を Secrets に保存することになり、結果誰でもボタンポチでリリースできるようになってしまいます。
まあそんなことする人はいないとは思いますが、やはりシステム的に管理者だけリリースできるようにしといたほうが安全でいいでしょう。
それをどう実現できるか色々調べましたが、結局 actions/github-script で actor の権限取得してチェックすることにしました。
jobs:
check-permission:
runs-on: self-hosted
steps:
- name: Check permission
uses: actions/github-script@v6
with:
script: |
const response = await github.request('GET /repos/{owner}/{repo}/collaborators/{username}/permission', {
owner: context.repo.owner,
repo: context.repo.repo,
username: context.actor
});
const permission = response.data.permission;
if (permission !== 'admin') core.setFailed(`User §{context.actor} does not have admin permissions.`);
else console.log(`User §{context.actor} has admin permissions.`);
これで admin
権限じゃない人はボタンポチしてもWorkflowがこけるようになりました。
パッケージ公開
権限確認ができたら、手動で打ってたコマンドを打ってくれるWorkflowを書きます。
release-package:
runs-on: self-hosted
needs: check-permission
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version-file: 'package.json'
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.6
- name: Install dependencies
run: pnpm install
- name: Create .npmrc file
run: |
echo "{諸々必要な情報と認証情報}" > .npmrc
- name: Build package
run: pnpm run build
- name: Publish package
run: pnpm run publish
公開できないところは省いてるので実際の yml とはちょっと差はありますけど、だいたいこんな感じで書いてます。
このWorkflowが正常終了したら、リリース自体は完了です。
リリースノート公開
リリースが終わったらリリースノートを公開しないとですね。
release-drafter のおかげでボタンポチでリリースノート公開できるようになりました…が、問題はその「ボタンポチしにいくのもめんどくね??リリース終わったら勝手にリリースノートも公開してほしいんだけど」って思ってしまったことです。
ということでリリースノート公開もWorkflowにやらせるために、色々調べて、また actions/github-script に頼ることにしました。
publish-reLease-note:
runs-on: self-hosted
needs: release-package
steps:
- name: Publish release note
uses: actions/github-scriptav6
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const { repo: { owner, repo }} = context;
const releases = await github.rest.repos.listReleases({ owner, геро });
const draftRelease = releases.data.find(release => release.draft === true);
if (!draftRelease) {
console.log('No draft release found to publish');
return
}
await github.rest.repos.updateRelease({
owner,
геро,
release_id: draftRelease.id,
draft: false
});
console.log(`Published release: ${draftRelease.name}`);
listReleases
を取得し、そこから draft
状態のものを抽出、draft
を false
にすることでリリースノート公開できました。
(私フロントエンドエンジニアなので Javascript 書いてるときがなんか落ち着きます、、)
Storybook アップデート
リリース完了したらリリースノート公開以外にも、社内向けで公開してる Storybook を更新する必要があります。
これが1番難しかったですね。
従来の Storybook の更新手順は新しい Storybook をビルドし、 リモートの Windows 共有フォルダに格納することです。手動リリースだと、開発環境も Windows なので簡単なコマンドでできてましたが、問題は github actions が linux で動いてることでした。
とういうことで今までのコマンドなんて使えなくなり、linux で Windows 共有フォルダにアクセスする方法を調べることになります。
結果、cifs-utils で Windows 共有フォルダマウントする方法を採用しました。
update-storybook:
runs-on: self-hosted
needs: release-package
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version-file: 'package.json'
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.6
- name: Install dependencies
run: pnpm install
- name: Install cifs-utils
run: |
sudo yum update -y
sudo yum install -y cifs-utils
# マウントするディレクトリ作成
- name: Create mount directory
run: mkdir -p $t{ github.workspace }}/mount
# 認証情報を入力、先ほど作成したディレクトリに Windows 共有フォルダをマウントする
- name: Mount network drive
run: sudo mount -t cifs -o username=${{ secrets. NETWORK_DRIVER_USER }}, password=${{ secrets. NETWORK_DRIVER_PASSWORD }}, vers=3.0 {マウントするWindows共有フォルダのパス} ${{ github.workspace }}/mount
# 古い Storybook ファイルを削除
- name: Remove old Storybook
run: sudo rm -rf ${{ github.workspace }}/mount/{Windows共有フォルダのStorybookのパス}
# 新しい Storybook をビルド
- name: Build Storybook
run: pnpm run build-storybook
# マウントしてきた Windows 共有フォルダにビルドしたStorybookをペースト
- name: Copy new Storybook to network drive
run: sudo cp -r ${{ github.workspace }}/{ビルドしたStorybookのパス} ${{ github.workspace }}/mount/{Windows共有フォルダのStorybookのパス}
# アンマウント
- name: Unmount network drive
if: always()
run: sudo umount ${{ github.workspace }}/mount
これでなんとか github actions でも Storybook アップデートができるようになり、一連のリリース作業を全部 Workflow にやらせることができました。
+)マウントしたら確実にアンマウントしよう
実は色々試行錯誤やってるうちに、↓のように書いてWorkflow実行してましたが、
- name: Mount network drive
run: sudo mount -t cifs -o username=${{ secrets. NETWORK_DRIVER_USER }}, password=${{ secrets. NETWORK_DRIVER_PASSWORD }}, vers=3.0 {マウントするWindows共有フォルダのパス} ${{ github.workspace }}/mount
# 他のステップたち、、
- name: Unmount network drive
run: sudo umount ${{ github.workspace }}/mount
マウントした直後のステップでエラーが発生して、そのままアンマウントせずジョブが終了され、、その後もう一度ジョブ実行したらアンマウントされてないことの影響でジョブが正常に動かなくなってしまいました。
マウントした後はもう何かが失敗しても絶対アンマウントは実行させないといけないことを身を持って知ったので、アンマウントは常に実行されるようにしました。
- name: Unmount network drive
if: always() # 必ず実行させる
run: sudo umount ${{ github.workspace }}/mount
🌷 終わり
さぐりさぐりでやった自動化なのでツッコミどころ多いと思いますし、これよりもっといい方法絶対あると思いますが、私がド素人な上に周りにも github actions 詳しい人がいなかったので今回はこれが最善でした。
それでも30分以上かかってたリリース作業がボタンポチで全部できるようになったので、まあ頑張ったのではないかと自分を慰めてます。
Discussion