Release Pleaseとembedパッケージを用いたGoのメタデータの管理
はじめに
私は普段 Go を用いて開発を行っているのですが,Go のプログラム中からアプリケーションのバージョンなどを取得したいと思うことが度々ありました.その際,以前までは go build
の引数で -ldflags
を指定することでコンパイル時にメタデータを埋め込んでいたのですが,Release Please とembed
パッケージを活用することでよりスマートにメタデータを扱うことができたので,紹介したいと思います.
ビルド時に埋め込むことのデメリット
Go のプログラム中からメタデータを扱う場合,静的なメタデータか動的に変わるメタデータかで扱い方が変わります.静的なメタデータの場合,プログラム中にハードコードすればよいですが,動的なメタデータの場合ビルド時に埋め込むなどの工夫が必要です.
ldflags について
Go build には-ldflags
と呼ばれるオプションが存在し,このオプション中で-X
を指定することで任意のパッケージ変数の値をビルド時に変更するということができます.-ldflags
を用いた変数の値の変更については詳しくは説明しないため.興味のある方は調べてみることをおすすめします.
ldflags の辛い点
-ldflags
によってメタデータの埋め込みは可能ですが,主に以下の点で辛いと思うことが有りました.
- コンパイルのオプションが長くなり,Dockerfile などでの可読性が悪い
-
go install
などを用いてインストールした場合,場合によってはメタデータが埋め込まれない. - メタデータを扱うパッケージをリファクタリングなどで移動させた場合,コンパイルオプションの変更が必要
- アプリケーションのバージョンを取得するためにスクリプトが必要となり,CI の定義ファイルや Dockerfile などが複雑になる.
特に,最近の Go では Go Module の導入により,パッケージ名が長くなりがちです.そのため,複数のメタデータを埋め込もうと思った時にコンパイルのコマンドが 5 行程度になることもしばしば発生します.
このような点から,ldflags を用いたメタデータの扱いから他の手段を用いてメタデータの埋込を行いたいと思っていました.
Release Please と embed パッケージを用いたメタデータの管理
前項で触れたような辛さがあったため,他に良い方法は無いかと考えていたところ Release Please と Go のembed
パッケージを組み合わせて見たところ,自分が感じていた課題などを全て解決してくれたためその方法について紹介したいと思います.
特に,私はアプリケーションのバージョンをプログラムに埋め込んでいたのですが,アプリケーションのバージョンが更新されたときにビルドオプションの修正をする必要がなく,スクリプトを用いてバージョンを取得する必要もなくなったため,CI や Dockerfile のロジックも単純にすることができました.
Release Please とは
Release Please は Google が開発しているツールで,Conventional commit に沿ったコミットを作成しているとコミットログを解析し,自動的に Release を作成してくれます.
embed パッケージについて
embed
パッケージは,Go の実行バイナリに任意のファイルを埋め込むことができるパッケージです.埋め込まれたファイルは[]byte
やembed.FS
として扱うことができ,os.Open
などでファイルを開いたときと同様に扱うことができます.また,ファイルを 1 つのみ埋め込む場合には,埋め込もうと思っているファイルが存在しない場合,コンパイルエラーが発生します.
メタデータの管理方法
まずは,以下のように管理したいメタデータに応じた json ファイルを用意します.
{
"version": "0.0.1"
}
次に,Go のプログラムを以下のように作成します.このとき,metadata.json
と同じ階層に Go のファイルを配置してください.
package metadata
import (
"encoding/json"
_ "embed"
)
//go:embed metadata.json
var raw []byte
var (
metadata Metadata
metadataOnce sync.Once
)
type Metadata struct {
Version string `json:"version"`
}
func GetMetadata() *Metadata {
metadata.Once(func(){
if err := json.Unmarshal(raw, &metadata); err != nil {
panic(err)
}
})
return &metadata
}
上記の用にすることで,メタデータをプログラムに埋め込むことができました.json を Go のMetadata
構造体にマッピングしているため,この構造体のフィールドを追加し,json のプロパティを変更することで,管理するメタデータの追加が容易にできます.
ですが,このままだとアプリケーションがリリースされてもバージョンは更新されません.そのため Release Please の機能を用いてリリース時にバージョンを更新するようにしたいと思います.
Release Please によるメタデータの更新
Release Please には任意ファイルの特定箇所を更新する機能があります.この機能を用いてmetadata.json
のversion
プロパティを変更させます.
以下のような,Release Please の設定ファイルをプロジェクトルートに用意します.
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"release-type": "go",
"include-component-in-tag": false,
"packages": {
".": {
"extra-files": [
{
"type": "json",
"path": "metadata.json",
"jsonpath": "$.version"
}
]
}
}
}
この中で特に重要なのは,extract-files
の中身です.ここに先ほど作成したmetadata.json
を指定することで Release Please によってリリース作成時にファイルが更新されます.path
プロパティを先ほど作成したmetadata.json
に合わせて変更するようにしてください.
json 以外にも yaml や toml なども更新できるそうなので,詳しくはドキュメントを参照してください.
最後に
今回は Go でのメタデータの管理として記事を書きましたが,ファイルの埋め込みさえできれば他の言語でもできると思います.また,Release Please を活用することでソースコードの値も変更できるので各プロダクトにあった方法を取れるとよいのかなと思います.
Discussion