リリースタグとリリースノート作成の半自動化でリリースサイクルを改善した話

こんにちは。エンジニアの濱田( @hamakou108 )です。

近年 DevOps の文脈で開発生産性の指標としてリリース頻度が注目されています。 DevOps Research and Assessment が提供している State of DevOps Report 2019 ではハイパフォーマンスな開発チームに顕著なメトリクスとして、リードタイムや復旧時間などと並びリリース頻度が紹介されています。 またリリース頻度の向上や付随する障害リスクの低下を図るためのプラクティスについて多くの議論が行われています。

M&Aクラウドでもリリースを高頻度かつスピーディーに実施するため、リリース作業をできるだけ自動化するように工夫しています。 以前にデプロイの半自動化の事例について鈴木から紹介しましたが、今回はリリースタグおよびリリースノートの作成などのリリース準備作業の半自動化を行った事例について紹介したいと思います。

リリース準備作業で抱えていた課題

M&Aクラウドの開発チームにはリリース準備の作業効率に関わる二つの問題がありました。

手作業に伴うミス

弊社ではブランチ管理の手法として Git Flow を採用しています。 Git Flow は gitflow コマンドを利用することで比較的簡単に実践することができますが、それでも作業時のミスは発生しがちです。 例えばローカルの develop ブランチの内容が古い状態で release ブランチを切ってしまったり、 release と hotfix の作業手順を混同してしまったり、タグ名の形式を誤ったり1など、数え上げたらキリがありません。。

また初めてリリース作業を行うメンバーの場合、ローカル環境で gitflow のインストールや初期化が済んでいないといった別の問題も発生していました。

このようにリリースの準備作業は全体的に注意が必要な心理的ハードルの高い作業になっていました。

作業時間

リリースブランチの作成、リリースに含まれる PR のリスト化、リリースブランチのマージ、タグ作成までの一連の作業をマニュアルで実施するとそれなりに時間が掛かります。 特にリリースノートの作成に関してはどの PR が含まれているかコミット履歴を辿らなければならず、面倒な作業でした。 これらの作業によってリリース作業の担当者は普段の開発に掛けられる時間が大幅に減ってしまうことも問題でした。

スクリプトによる作業自動化

これらの作業手順はほぼ定型化されていたため、大半の作業をシェルスクリプトで自動化することで課題解決を図りました。 幾つかキーポイントとなる部分をコードスニペットと共に見ていきたいと思います。

事前要件のチェック

function checkRequirements() {
  if [[ ! "$OSTYPE" == "darwin"* ]]; then
    echo "MacOS を利用してください。" >&2; exit 1
  fi

  if ! type git > /dev/null; then
    echo "Git をインストールしてください。" >&2; exit 1
  fi

  if ! brew ls --versions git-flow > /dev/null; then
    echo "Git Flow をインストールしてください。" >&2; exit 1
  fi
}

function checkoutAndPull() {
  git checkout master
  git pull upstream master
  git checkout develop
  git pull upstream develop
  git fetch upstream
}

checkRequirements() では Git や gitflow など必要なツールがインストールされているか確認しています。 見て分かる通り MacOS に限定した実装にしていますが、これは現時点で開発者全員が Mac ユーザーであるためです。 複数の OS に対応した汎用性の高い実装にすることも可能でしたが、直接的に事業価値に貢献するコードではないので少ないコストで実装できる仕様を採用しました。 YAGNI です。

さらに checkoutAndPull() ではリモートリポジトリの pull を行い、ローカルリポジトリの内容が古いまま作業してしまうことを防止しています。

リリースに含まれる PR のタイトルとリンクの抽出

function printPullRequestToBeReleased() {
  local repositoryName
  local prevRelease

  repositoryName=${1}

  read -r -p "${repositoryName} の前回のリリースタグを指定してください: " prevRelease
  echo "${prevRelease}"
  git log "${prevRelease}".. --merges --first-parent --reverse --pretty=format:"* %b %s" | \
    REPO="${repositoryName}" perl -ple 's/Merge.*#(\d*).*$/(https:\/\/github.com\/your-organization-name\/$ENV{REPO}\/pull\/$1)/' | \
    perl -ple 's/\*\s(.*)\(h/\* [$1]\(h/'
  printf "\n\n"
  read -r -p "上記のリストをコピーしておいてください"
}

printPullRequestToBeReleased() では前回のリリースタグ名を指定するとそこから最新のコミットまでに含まれる PR のタイトルと URL をコミット本文から抽出して Markdown のリスト形式で出力します。 出力されたリストをそのまま利用するだけで簡単にリリースノートが作れます。

release ブランチの作成、マージ、タグ作成

function startRelease() {
  local repositoryName
  local currentRelease

  repositoryName=${1}

  read -r -p "${repositoryName} の「今回の」リリースタグを指定してください。空なら今日の日付が使われます: " currentRelease
  if test -z "${currentRelease}"; then
    currentRelease=$(date "+%Y-%m-%d")
  fi
  echo "${currentRelease}"

  git flow release start "${currentRelease}"
  git push upstream "release/${currentRelease}"
}

function finishRelease() {
  local repositoryName
  local releaseBranch

  repositoryName=${1}
  releaseBranch=$(git branch | egrep -o "release\/[0-9]{4}-[0-9]{2}-[0-9]{2}.*")

  if [[ -z "${releaseBranch}" ]]; then
    echo "リリースブランチが存在しません。" >&2; exit 1
  fi

  git checkout "${releaseBranch}"
  git pull upstream "${releaseBranch}"
  git flow release finish "$(echo "${releaseBranch}" | perl -ple 's/release\/(.*)/$1/')"
  git push upstream master
  git push upstream --tags
  git push upstream develop
  git push upstream --delete "${releaseBranch}"
}

startRelease() で release ブランチの作成を行います。 特に指定がなければブランチ名は release/YYYY-MM-DD のような形式で作成され、忘れがちなリモートリポジトリへの push も自動化しています 2

また finishRelease() で release ブランチのマージ、タグ作成もスクリプト化しました。

スクリプト導入の結果

スクリプトを作成したことで前述した課題を次のように解決することができました。

  • 事前要件を満たせていない場合はスクリプト実行不可とすることで環境依存の問題を防止。
  • Git 操作をスクリプト内で完結させることで手順の誤りによるミスを解消。
  • リリースノートの作成なども含め、自動化した部分の作業時間は1-2分にまで短縮。

まとめ

リリースタグおよびリリースノートの作成をスクリプトで半自動化することで、環境依存や手作業によるミスを軽減し、作業時間も短縮することができました。 作業効率に加えて DX も向上させることができましたが、まだ幾つかマニュアル作業も残っているので今後もリリースサイクルの効率化を適宜進めていきたいと思います。


  1. 弊社ではタグ名を特定のフォーマットとした場合に CircleCI のリリース専用ワークフローが実行されるようにしているため、名前を間違えるとタグを切り直さなければならない場合があります。

  2. ちなみに hotfix のフローに関してもほぼ同様にスクリプト化してあり、リリースフローによって使い分けています。