💨

HCLファイルを hashicorp/hcl で読み書きする

2023/03/05に公開

HCLファイルをプログラムから読み書きする方法として hashicorp/hcl がある。
ここのパッケージに一通りの関数がそろっているのでこれを試してみる。

hclsimple

hclsimpleはHCLファイルをパースしてGoの構造体に読み込むパッケージ。
タグ情報付きの構造体を渡して読み込むので、事前にファイルの構造を定義しておく必要がある。

type Config struct {
	LogLevel string `hcl:"log_level"`
}

var config Config

// config.hclファイルから読んでデコードする
err := hclsimple.DecodeFile("config.hcl", nil, &config)
if err != nil {
	fmt.Printf("Failed to load configuration: %s\n", err)
	return
}
fmt.Printf("Configuration in config.hcl is %#v\n", config)

ファイルからではなく変数から読み込む場合はDecode関数を利用する。

type Config struct {
	LogLevel string `hcl:"log_level"`
}

var config Config
var hclBody = `
log_level = "info"
`

// hclBody変数から読んでデコードする
err = hclsimple.Decode("config.hcl", []byte(hclBody), nil, &config)
if err != nil {
    fmt.Printf("Failed to load configuration: %s\n", err)
    return
}
fmt.Printf("Configuration is %#v\n", config)

hclparse

hclparseはファイルをパースしてHCLのシンタックスチェックを行う。また、パースしたファイル名と内容の対応関係を管理する。
シンタックスチェック自体はhclsyntaxパッケージで行うが、基本的にはhclparse経由で利用する。

p := hclparse.NewParser()

file, diags := p.ParseHCLFile("sample.hcl")
if diags.HasErrors() {
    fmt.Println(diags.Error())
    return
}
fmt.Printf("%s", file.Bytes)

// Filesではファイル名とコンテンツのmapが返る
for k, v := range p.Files() {
	fmt.Printf("filename: %s\n", k)
	fmt.Printf("contents: %s\n", v.Bytes)
}

gohcl

gohcl はHCLデータをパースしてGoの構造体に読み込むパッケージ。
hclsimpleはファイルや[]byteのデータを読み込んでパースするが、gohclはhcl.Bodyからデータを読み込んでパースする点が異なる。

var config Config
var varConfig = `
log_level = "info"
`

file, diags := hclsyntax.ParseConfig([]byte(varConfig), "config.hcl", hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
    fmt.Printf("failed to parse file %s\n", diags)
    return
}
diags = gohcl.DecodeBody(file.Body, nil, &config)
if diags.HasErrors() {
	fmt.Printf("failed to decode %s\n", diags)
	return
}
fmt.Printf("config file is %#v\n", config)

hclwrite

hclwriteはHCLデータを生成するたに利用するパッケージ。
key = value 形式のAttribute、res {...} 形式のBlock、複数のAttributeやBlockで構成されるBodyでHCLデータを構築する。
また、型システムを実現するためにctyを利用しており、hclwriteでは特にctyで型を指定してデータを構築する。

f := hclwrite.NewEmptyFile()
rootBody := f.Body()

// foo = "bar" を構築する
rootBody.SetAttributeValue("foo", cty.StringVal("bar"))

// 以下のHCLを構築する
// resource "aws_s3_bucket" "this" {
//   bucket = "sample-bucket-name"
//   tags = {
//     baz = true
//     foo = 10
//   }
// }
block := hclwrite.NewBlock("resource", []string{"aws_s3_bucket", "this"})
blockBody := block.Body()
blockBody.SetAttributeValue("bucket", cty.StringVal("sample-bucket-name"))
blockBody.SetAttributeValue("tags", cty.ObjectVal(map[string]cty.Value{
	"baz": cty.True,
	"foo": cty.NumberIntVal(10),
}))
rootBody.AppendBlock(block)

fmt.Printf("%s", f.Bytes())

Discussion