Release Pleaseとembedパッケージを用いたGoのメタデータの管理

2024/09/28に公開

はじめに

私は普段 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 とは

https://github.com/googleapis/release-please

Release Please は Google が開発しているツールで,Conventional commit に沿ったコミットを作成しているとコミットログを解析し,自動的に Release を作成してくれます.

embed パッケージについて

https://pkg.go.dev/embed

embedパッケージは,Go の実行バイナリに任意のファイルを埋め込むことができるパッケージです.埋め込まれたファイルは[]byteembed.FSとして扱うことができ,os.Openなどでファイルを開いたときと同様に扱うことができます.また,ファイルを 1 つのみ埋め込む場合には,埋め込もうと思っているファイルが存在しない場合,コンパイルエラーが発生します.

メタデータの管理方法

まずは,以下のように管理したいメタデータに応じた json ファイルを用意します.

metadata.json
{
  "version": "0.0.1"
}

次に,Go のプログラムを以下のように作成します.このとき,metadata.jsonと同じ階層に Go のファイルを配置してください.

metadata.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.jsonversionプロパティを変更させます.

以下のような,Release Please の設定ファイルをプロジェクトルートに用意します.

release-please-config.json
{
  "$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 を活用することでソースコードの値も変更できるので各プロダクトにあった方法を取れるとよいのかなと思います.

GitHubで編集を提案

Discussion