go-assets を利用して toml 形式のファイルをシングルバイナリに含める方法

Written by @ryysud

May 20, 2018 22:00 · 1589 words · 4 minutes read #golang

go-assets ってなに

指定されたリソース(今回で言うところの toml 形式のファイル)をバイト列に変換して、そのデータにアクセスするためのメソッドと一緒にGoのソースコードとして生成してくれるもの。

上記のような説明で go-bindata の代替として紹介されていることが多く、最初は意味不明に感じる(現に自分がそうだった)と思いますが、実際に手を動かして触ってみると「ああ、なるほどね」となるので、是非試してみて下さい。

必要なパッケージ

jessevdk/go-assets-builder - Simple assets builder program for go-assets

jessevdk/go-assets の README にも記載されているとおりで go-assets を利用したGoのソースコードを生成してくれる CLI で、これがあれば何も考えること無くリソースをシングルバイナリに含めることが可能になります。

BurntSushi/toml - TOML parser for Golang with reflection

大人気の toml のパーサーです。日本語で紹介されているものだとこれが1番多いかも。

実際にやってみる

まずはシングルバイナリに含めたい toml 形式のファイルを用意します

$ cat config/config.toml
[sample]
string = "Hello Toml!"
int = 0
float = 1.0
bool = true
array = ["pen", "pineapple", "apple", "pen"]

go-assets-builder を go get して必要に応じてパラメータを渡して Go のソースコードを生成します

$ go get github.com/jessevdk/go-assets-builder
$ go-assets-builder -p config -o config/assets.go -s /config config/config.toml

# こんなパラメータがあります
$ go-assets-builder -h
Usage:
  go-assets-builder [OPTIONS] FILES...

Application Options:
  -p, --package=      The package name to generate the assets for (default: main)
  -v, --variable=     The name of the generated asset tree (default: Assets)
  -s, --strip-prefix= Strip the specified prefix from all paths
  -o, --output=       File to write output to, or - to write to stdout (default: -)

Help Options:
  -h, --help          Show this help message

実際に生成された Go のソースコードがこちら

$ cat config/assets.go
package config

import (
        "time"

        "github.com/jessevdk/go-assets"
)

var _Assets39fc262c9ed6747f7de65d53767961cfe0f6f43c = "[sample]\nstring = \"Hello Toml!\"\nint = 0\nfloat = 1.0\nbool = true\narray = [\"pen\", \"pineapple\", \"apple\", \"pen\"]\n"

// Assets returns go-assets FileSystem
var Assets = assets.NewFileSystem(map[string][]string{"/": []string{"config.toml"}}, map[string]*assets.File{
        "/": &assets.File{
                Path:     "/",
                FileMode: 0x800001ed,
                Mtime:    time.Unix(1526826787, 1526826787000000000),
                Data:     nil,
        }, "/config.toml": &assets.File{
                Path:     "/config.toml",
                FileMode: 0x1a4,
                Mtime:    time.Unix(1526825697, 1526825697000000000),
                Data:     []byte(_Assets39fc262c9ed6747f7de65d53767961cfe0f6f43c),
        }}, "")

config/config.toml がバイト列としてソースコードに埋め込まれていることがわかります。また Assets.Open() という関数を使ってファイルをオープンすることが可能になっています。

https://godoc.org/github.com/jessevdk/go-assets#FileSystem.Open

Assets.Open() でオープンされたファイルは、ファイルシステムとしてのインターフェースを持った http.File 型として返されるので、とても扱いやすいです。

https://golang.org/pkg/net/http/#File

main.go を作成して、go-assets-builder によって生成されたファイル経由でバイナリとなった config/config.toml を参照してみます。

$ cat main.go
package main

import (
        "fmt"

        "github.com/ryysud/handle-toml-with-go-assets/config"
)

func main() {
        f, err := config.Assets.Open("/config.toml")
        if err != nil {
                panic(err)
        }
        defer f.Close()

        fmt.Println(f)
}

$ go run main.go
&{/config.toml -rw-r--r-- 2066-10-07 04:29:54 +0000 UTC [91 115 97 109 112 108 101 93 10 115 116 114 105 110 103 32 61 32 34 72 101 108 108 111 32 84 111 109 108 33 34 10 105 110 116 32 61 32 48 10 102 108 111 97 116 32 61 32 49 46 48 10 98 111 111 108 32 61 32 116 114 117 101 10 97 114 114 97 121 32 61 32 91 34 112 101 110 34 44 32 34 112 105 110 101 97 112 112 108 101 34 44 32 34 97 112 112 108 101 34 44 32 34 112 101 110 34 93 10] 0xc4200ee260 0xc420072d20 0}

バイト列となっており(自分を含めた多くの人間は)中身を把握するのは難しいですが、値は取得できているようなので、このバイト列となった toml 形式のファイルをパースしていこうと思います。

まずは toml 形式のファイルに対応する構造体を用意します。型は宣言している値に対応するものを指定してください。

$ cat models/config.go
package models

type Config struct {
        Sample Sample
}

type Sample struct {
        String string
        Int    int
        Float  float32
        Bool   bool
        Array  []string
}

BurntSushi/toml を go get して toml 形式のファイルをパースするためのロジックを main.go に追加します。

$ go get github.com/BurntSushi/toml

$ cat main.go
package main

import (
        "fmt"
        "reflect"

        "github.com/ryysud/handle-toml-with-go-assets/config"
        "github.com/ryysud/handle-toml-with-go-assets/models"

        "github.com/BurntSushi/toml"
)

func main() {
        f, err := config.Assets.Open("/config.toml")
        if err != nil {
                panic(err)
        }
        defer f.Close()

        var config models.Config
        _, err = toml.DecodeReader(f, &config)
        if err != nil {
                panic(err)
        }

        printWithType(config.Sample.String)
        printWithType(config.Sample.Int)
        printWithType(config.Sample.Float)
        printWithType(config.Sample.Bool)
        printWithType(config.Sample.Array)
}

func printWithType(x interface{}) {
        fmt.Printf("type: %s, value: %#v\n", reflect.TypeOf(x), x)
}

先述の通りで Assets.Open() でオープンされたファイルは、ファイルシステムとしてのインターフェースを持った http.File 型として返されるため、io.Reader 型を引数にとる toml.DecodeReader にそのまま投げることが可能です。

https://godoc.org/github.com/BurntSushi/toml#DecodeReader

実行してみると toml 形式のファイルを参照出来ていることわかります。

$ go run main.go
type: string, value: "Hello Toml!"
type: int, value: 0
type: float32, value: 1
type: bool, value: true
type: []string, value: []string{"pen", "pineapple", "apple", "pen"}

# シングルバイナリに含めた toml 形式のファイル
# [sample]
# string = "Hello Toml!"
# int = 0
# float = 1.0
# bool = true
# array = ["pen", "pineapple", "apple", "pen"]

まとめ

このように簡単に toml 形式の値をシングルバイナリに含めることが可能です。また、toml 形式のファイルでなくても、パーサーさえあれば、JSON や YAML など他形式のファイルも同じような手順を踏めば、実現可能だと思うので色々試してみて下さい。

さいごに

今回紹介したコードは GitHub で公開しておりますのでご興味ある方はどうぞ〜

https://github.com/ryysud/handle-toml-with-go-assets

※ GitHub の方では go generate 経由で go-assets-builder を実行しています