Go 言語で、Viper を使わず、生の HCL をinterface{}
ではなく、特定の構造体にパースしたいと思ったので、サンプルを書いてみました。
Using HCL - Part 1 - Introductionを参考にしました。
ターゲットの HCL
とても簡単ですが、こちらのHCL
variable "azure" {
description = "This is Azure"
}
こちらは下記の json
と等価です。やはり、HCLは100倍読みやすい、、、
{
"variable": {
"azure": {
"description": "This is Azure"
}
}
構造体
上記の HCL をパースするための構造体を定義します。
type Directories struct {
Name string `hcl:",key"`
Description string
}
type Config struct {
Variable []Directories
}
実際に実施してみると、ポイントは二つありました。Config
が起点になります。実際のdescription
は一つしかありませんがDirectories
は配列にしないとうまく Parse できません。
これは上記のブログが参考になります。
directory "config" {
source_dir = "/etc/eventstore"
dest_prefix = "escluster/config"
exclude = []
pre_backup_script = "before_backup.sh"
post_backup_script = "after_backup.sh"
pre_restore_script = "before_restore.sh"
post_restore_script = "after_restore.sh"
}
の部分は下記のようなオブジェクトに Parse されます。下記のオブジェクトを見ると、directory
は、map になっており、そのValue の部分は、([]*hcl.Object)
の形式になっています。ですので、この部分は、配列でないとうまくパースできないことになります。
(*hcl.Object)(0x82035e840)({
Key: (string) (len=9) "directory",
Type: (hcl.ValueType) ValueTypeObject,
Value: ([]*hcl.Object) (len=1 cap=1) {
(*hcl.Object)(0x82035e810)({
Key: (string) (len=6) "config",
Type: (hcl.ValueType) ValueTypeObject,
Value: ([]*hcl.Object) (len=7 cap=7) {
(*hcl.Object)(0x82035e6c0)({
Key: (string) (len=10) "source_dir",
Type: (hcl.ValueType) ValueTypeString,
Value: (string) (len=15) "/etc/eventstore",
Next: (*hcl.Object)(<nil>)
}),
もう一点は、下記のazure
を取得する方法です。こちらも上記の生オブジェクトの結果が参考になります。上記のサンプルは、config
という値は、Keyに格納されています。
variable "azure" {
description = "This is Azure"
}
ですので、Nameを hcl の Key から取得します。
type Directories struct {
Name string `hcl:",key"`
Decode の実行
Decode の実行は、Decode
関数で行います。
var conf Config
:
err = hcl.Decode(&conf, string(b))
第一引数に、構造体のポインタを渡して、2つ目にパースしたい文字列を渡します。
これでめでたく Decode できました。
$ go run main.go
Decoding...
variable "azure" {
description = "This is Azure"
}
This is Azure
azure
コード全体
package main
import (
"fmt"
"io/ioutil"
"github.com/hashicorp/hcl"
)
/// This sample test read/write from HCL file.
func main() {
type Directories struct {
Name string `hcl:",key"`
Description string
}
type Config struct {
Variable []Directories
}
// var conf map[string]map[string]string
var conf Config
b, err := ioutil.ReadFile("sample.hcl")
if err != nil {
panic(err)
}
fmt.Println("Decoding...\n")
fmt.Println(string(b))
err = hcl.Decode(&conf, string(b))
fmt.Println(conf.Variable[0].Description)
fmt.Println(conf.Variable[0].Name)
if err != nil {
panic(err)
}
}
今回の反省と改善のヒント
こんな簡単なものに数時間かかった。考えられる原因は
- しばらく触っておらず、go の文法(構造体とポインタ、ファイルアクセス)を忘れた
- HCLのドキュメントやサンプルが見つからず、テストを読んだ
- HCL の Decode の仕様がよくわからなかった(テストは、
interface{}
に対するパースになっている) - 上記のを理解するために、結局他人のブログで、生パースをしているコードと結果が参考になった
次回からの改善としては
- Go の基本文法は忘れたのなら、まず思い出す。よくコードを書くようにする
hcl:", key"
の記述は、json
のパースの時に使われるものと同じノリでで、HCLに実装されていると思われる。参考。この記法は、Struct の Tagという記法。
タグは、下記のようにリフレクションでLookupできる様子。
type T struct {
f string `one:"1" two:"2"blank:""`
}
func main() {
t := reflect.TypeOf(T{})
f, _ := t.FieldByName("f")
fmt.Println(f.Tag) // one:"1" two:"2"blank:""
v, ok := f.Tag.Lookup("one")
実際のコードだと、この付近で、tagを参照している。
多分、このあたりのコードを読んでよくわからない文法があったら、ハローワールドを作っていって理解したら次回はこの手のコードは高速に書けそう。