こんにちは!スタッフエンジニアの @kenkoooo です。今回は、Homebrew を使って社内ツールを簡単に配布する方法を紹介します。
社内ツール開発
社内で使用する便利ツールを開発する方法はいくつかあります。
- 社員だけがアクセスできるウェブアプリ
- ブラウザの拡張機能
- ダウンロードしてローカルで動かす実行可能ファイル
- etc.
どのPCにもブラウザはインストールされているため、ユーザーに実行環境を用意する負担をかけないという点では、ウェブアプリやブラウザ拡張機能が適しているでしょう。一方で、ブラウザで動作しないローカル開発用のCLIツールなど、実行可能ファイルを配布するしかない場合もあります。
CLIツールを配布する際、毎回「tar.gzをダウンロードして展開し、出てきたファイルをPATHが通っている場所に置いてください」と指示するのは手間がかかりますし、こういった作業に慣れていない社員もいるでしょう。コマンド一つでインストールできるのが理想です。
Homebrew
macOS では、サードパーティ製ツールをインストールするために Homebrew というパッケージマネージャが広く使われています。公式のツールではありませんが、エンジニアで Mac を使っている人の大半がインストールしているのではないでしょうか。
Homebrew を使うことで、サードパーティのツールをコマンド一発でインストールできます。Homebrew のパッケージの定義は Formula と呼ばれる Ruby ファイルに書かれていて、デフォルトの設定では公式リポジトリに Formula があればインストールできます。このリポジトリにプルリクエストを出して新しい Formula を追加することもできます。
このような Formula を集めたリポジトリを tap と言います。Homebrew では、デフォルトの tap の他にサードパーティの tap を追加することができます。有名なものでは、AWS の aws/homebrew-tap があります。この tap を追加し、aws/copilot-cli というツールをインストールするのは以下のようにしてできます。
# brew tap [tap の GitHub リポジトリ名] で tap を追加できます # 先頭の homebrew- は省略できるので aws/homebrew-tap と aws/tap は同じです brew tap aws/tap # brew install [tap 名]/[ツール名] でツールをインストールできます # aws/tap 内の copilot-cli というツールは以下のようにインストールできます brew install aws/tap/copilot-cli
このように、配布したいツールの情報を記載した Formula と、それを配信するための tap を用意することで、Homebrew ユーザーにツールを配布することができます。ユーザー側はその tap を追加しておけば、brew コマンド一発でツールを簡単にインストールできます。
GitHub プライベートリポジトリからの配布
上記の方法で自作のツールを配布することで、brew コマンドでインストールできるようになりますが、社内ツールを配布する場合、tap もツールもプライベートリポジトリにあるはずです。GitHub プライベートリポジトリからツールを配布するにはひと工夫必要になります。
[ユーザー側] Personal Access Token の設定
Homebrew の実行時に、GitHub の Personal Access Token を環境変数に設定するようにします。これは以下のステップで行います。
GitHub から Personal Access Token を取得します。
- https://github.com/settings/tokens
repo
スコープが付与されている必要があります。
取得したトークンを
HOMEBREW_GITHUB_API_TOKEN
として環境変数に設定します。/etc/homebrew/brew.env
などのファイルに記載することで設定できます。- 詳細はこちら:https://docs.brew.sh/Manpage#environment
この設定を行うと、brew コマンドが Personal Access Token を使って GitHub にアクセスするようになります。これで自作の tap を brew tap
コマンドで追加できます。
[配布側] Formula の作成
配布するツールの Formula を作成します。ファイルをダウンロードしてきてインストールする Formula は、大まかに以下のような形式です。
class MyCoolTool < Formula version <バージョン番号> url <ダウンロードするファイルのURL> sha256 <ダウンロードするファイルのSHA256チェックサム> def install bin.install <インストールする実行可能ファイルの名前> end end
url と sha256 を指定することで、ファイルをダウンロードしてインストールすることができます (詳細はこちら)。また、 bin.install
を使うことで実行可能権限をつけて bin ディレクトリに入れてくれます (詳細はこちら)。
ここで配布するツールは my-cool-tool
という名前で、以下のようなものとします。
my-cool-tool
という実行可能ファイルが tar.gz に固められている。- Linux (x86) 用と macOS (ARM) 用の2種類の実行可能ファイルが用意されている。
- 2種類の .tar.gz ファイルは両方とも GitHub Releases にアップロードされている。
これらの条件下でツールを配布する Formula は次のようになります。
require_relative './lib/strategy' # GitHubPrivateRepositoryReleaseDownloadStrategy を読み込む class MyCoolTool < Formula version 'v0.1' on_macos do if Hardware::CPU.arm? url 'https://github.com/estie-inc/my-cool-tool/releases/download/v0.1/my-cool-tool-macos-arm64.tar.gz', using: GitHubPrivateRepositoryReleaseDownloadStrategy sha256 'f0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde' def install bin.install 'my-cool-tool' end end end on_linux do if Hardware::CPU.intel? url 'https://github.com/estie-inc/my-cool-tool/releases/download/v0.1/my-cool-tool-linux-amd64.tar.gz', using: GitHubPrivateRepositoryReleaseDownloadStrategy sha256 '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' def install bin.install 'my-cool-tool' end end end end
OS および CPU アーキテクチャごとに条件分岐し、それぞれ異なる URL からファイルをダウンロードしてインストールしています。
url を指定してファイルをダウンロードする際に、using でStrategy を指定することで、必要な処理を組み込むことができます。プライベートリポジトリの Releases からファイルをダウンロードするため GitHubPrivateRepositoryReleaseDownloadStrategy
という自前のクラスを作成し、これを使用しています。こちらの中身は次のようになっています。
class GitHubPrivateRepositoryReleaseDownloadStrategy < CurlDownloadStrategy require 'utils/formatter' require 'utils/github' def initialize(url, name, version, **meta) super parse_url_pattern set_github_token end private def _fetch(url:, resolved_url:, timeout:) curl_download download_url, '--header', 'Accept: application/octet-stream', to: temporary_path, timeout: timeout end def download_url "https://#{@github_token}@api.github.com/repos/#{@owner}/#{@repo}/releases/assets/#{asset_id}" end def parse_url_pattern url_pattern = %r{https://github.com/([^/]+)/([^/]+)/releases/download/([^/]+)/(\S+)} raise CurlDownloadStrategyError, 'Invalid url pattern for GitHub Release.' unless @url =~ url_pattern _, @owner, @repo, @tag, @filename = *@url.match(url_pattern) end def set_github_token @github_token = ENV['HOMEBREW_GITHUB_API_TOKEN'] unless @github_token raise CurlDownloadStrategyError, 'Environmental variable HOMEBREW_GITHUB_API_TOKEN is required.' end validate_github_repository_access! end def validate_github_repository_access! # Test access to the repository GitHub.repository(@owner, @repo) rescue GitHub::HTTPNotFoundError # We only handle HTTPNotFoundError here, # because AuthenticationFailedError is handled within util/github. message = <<~EOS HOMEBREW_GITHUB_API_TOKEN can not access the repository: #{@owner}/#{@repo} This token may not have permission to access the repository or the url of formula may be incorrect. EOS raise CurlDownloadStrategyError, message end def asset_id @asset_id ||= resolve_asset_id end def resolve_asset_id release_metadata = fetch_release_metadata assets = release_metadata['assets'].select { |a| a['name'] == @filename } raise CurlDownloadStrategyError, 'Asset file not found.' if assets.empty? assets.first['id'] end def fetch_release_metadata GitHub.get_release(@owner, @repo, @tag) end end
Homebrew のライブラリとして、URL を指定してファイルをダウンロードする CurlDownloadStrategy
が用意されているので、これを拡張することにします。
先ほどの Formula で Releases のブラウザ用 URL を指定していましたが、GitHub API 経由で Releases からファイルをダウンロードするのにはこの URL を使うことができません。そこで、この Strategy 内では与えられたブラウザ用の URL をパースして必要な情報を抜き出し、さらに GitHub API にアクセスして API アクセス用の URL を組み立てるのに必要な Asset ID というのを取得し、これらを組み合わせて API アクセス用の URL を組み立ててからダウンロードしています。API 用の URL に Personal Access Token も必要なので、 HOMEBREW_GITHUB_API_TOKEN
からトークンを取り出して URL に含めます。
この自前の Strategy を Formula に含めることで、プライベートリポジトリの Releases にあるファイルを Personal Access Token を使って GitHub API 経由でダウンロードすることができるようになります。
まとめ
配信側は Formula を用意し、それを入れた tap を用意する必要があります。
Formula 内では OS や CPU に合わせてダウンロードする URL を切り替えることができます。
Homebrew がプライベートリポジトリからファイルをダウンロードできるように、CurlDownloadStrategy を拡張した Strategy を定義し、Formula から使います。
ユーザー側は Personal Access Token を Homebrew にセットし、tap を追加する必要があります。
ここまでやると、
brew install my-cool/tap/my-cool-tool
といったようにコマンド一発でツールをインストールできるようになります。
最後に
estie では、全社横断の技術的な課題を解決するために、自前のツールを開発・配布することがあります。社内用の tap はすでにありますので、ガンガン開発してくださる方をお待ちしています。