go hcl 解析
HCL 是 hashicorp 推出的一个配置语言,在 hashicorp 的产品,如 Consul、Terraform 中用作标准的配置语言,其语法结构有点类似于 nginx 的配置文件,一个简单的例子如下:
table = "filter"
enabled = ["mon","osd"]
disabled = []
rule "ipv4" "mon" {
priority = 100
tcp = [
"-p tcp -m multiport --dport 6789,3300 -m state --state NEW -j ACCEPT",
"-p tcp -m multiport --dport 6789,3300 -m state --state ESTABLISHED -j ACCEPT",
]
}
rule "ipv4" "osd" {
priority = 200
tcp = [
"-p tcp -m multiport --dport 6800:7300 -m state --state NEW -j ACCEPT",
"-p tcp -m multiport --dport 6800:7300 -m state --state ESTABLISHED -j ACCEPT",
]
}
针对 hcl 文档的操作,官方的 hashicorp/hcl2 库提供了非常完善的支持,本文用到的仅是该库关于 hcl 文档解析里非常、非常小的一部分。
hcl 文档结构
hcl 文档由顶层的 attribute(s) 和 block(s) 组成;
block 由 block header 以及内层的 attribute(s) 和 block(s) 组成;
block header 由 type 和 label(s) 组成。
hcl 文档解析上层接口
可以使用上层的接口,以类似 json、yaml 的方式进行解析,直接从 hcl 文档 decode 到 Go 数据结构,可以参考如下的例子:
package main
import (
"github.com/hashicorp/hcl2/gohcl"
"github.com/hashicorp/hcl2/hclparse"
"github.com/pkg/errors"
"github.com/sanity-io/litter"
"log"
)
const data = `table = "filter"
enabled = ["mon","osd"]
disabled = []
ipv4 "mon" {
priority = 100
tcp = [
"-p tcp -m multiport --dport 6789,3300 -m state --state NEW -j ACCEPT",
"-p tcp -m multiport --dport 6789,3300 -m state --state ESTABLISHED -j ACCEPT",
]
}
ipv6 "osd" {
priority = 200
tcp = [
"-p tcp -m multiport --dport 6800:7300 -m state --state NEW -j ACCEPT",
"-p tcp -m multiport --dport 6800:7300 -m state --state ESTABLISHED -j ACCEPT",
]
}
`
type Config struct {
Table *string `hcl:"table,attr"`
Enabled []string `hcl:"enabled,attr"`
Disabled []string `hcl:"disabled,optional"`
RulesV4 []Rule `hcl:"ipv4,block"`
RulesV6 []Rule `hcl:"ipv6,block"`
}
type Rule struct {
Name string `hcl:"name,label"`
Priority *int `hcl:"priority,optional"`
TCP []string `hcl:"tcp,optional"`
UDP []string `hcl:"udp,optional"`
}
func decode() (Config, error) {
parser := hclparse.NewParser()
file, diags := parser.ParseHCL([]byte(data), "literal-document")
if file == nil || file.Body == nil {
return Config{}, errors.Wrap(diags, "failed to parse hcl document")
}
var config Config
if diags := gohcl.DecodeBody(file.Body, nil, &config); diags != nil {
return Config{}, errors.Wrap(diags, "failed to decode hcl document")
}
return config, nil
}
func main() {
config, err := decode()
if err != nil {
log.Fatal(err)
}
litter.Dump(config)
}
hcl 文档解析底层接口
也可以根据 hcl 文档的结构使用底层接口进行更灵活的解析,可以参考如下的例子:
package main
import (
"github.com/hashicorp/hcl2/gohcl"
"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hclparse"
"github.com/pkg/errors"
"github.com/sanity-io/litter"
"log"
)
const data = `table = "filter"
enabled = ["mon","osd"]
disabled = []
ipv4 "mon" {
priority = 100
tcp = [
"-p tcp -m multiport --dport 6789,3300 -m state --state NEW -j ACCEPT",
"-p tcp -m multiport --dport 6789,3300 -m state --state ESTABLISHED -j ACCEPT",
]
}
ipv6 "osd" {
priority = 200
tcp = [
"-p tcp -m multiport --dport 6800:7300 -m state --state NEW -j ACCEPT",
"-p tcp -m multiport --dport 6800:7300 -m state --state ESTABLISHED -j ACCEPT",
]
}
`
type Config struct {
Table *string
Enabled []string
Disabled []string
RulesV4 []Rule
RulesV6 []Rule
}
type Rule struct {
Name string
Priority *int
TCP []string
UDP []string
}
var fileSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "table",
},
{
Name: "enabled",
},
{
Name: "disabled",
Required: false,
},
},
Blocks: []hcl.BlockHeaderSchema{
{
Type: "ipv4",
LabelNames: []string{"name"},
},
{
Type: "ipv6",
LabelNames: []string{"name"},
},
},
}
var ruleBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "priority",
Required: false,
},
{
Name: "tcp",
Required: false,
},
{
Name: "udp",
Required: false,
},
},
}
func decode() (Config, error) {
parser := hclparse.NewParser()
file, diags := parser.ParseHCL([]byte(data), "literal-document")
if file == nil || file.Body == nil {
return Config{}, errors.Wrap(diags, "failed to parse hcl document")
}
content, diags := file.Body.Content(fileSchema)
if content == nil {
return Config{}, errors.Wrap(diags, "failed to decode hcl document")
}
var config Config
for _, attr := range content.Attributes {
switch attr.Name {
case "table":
attrDiags := gohcl.DecodeExpression(attr.Expr, nil, &config.Table)
diags = append(diags, attrDiags...)
case "enabled":
attrDiags := gohcl.DecodeExpression(attr.Expr, nil, &config.Enabled)
diags = append(diags, attrDiags...)
case "disabled":
attrDiags := gohcl.DecodeExpression(attr.Expr, nil, &config.Disabled)
diags = append(diags, attrDiags...)
default:
// Should never happen because the above cases should be exhaustive
// for all attribute type names in our schema.
continue
}
}
for _, block := range content.Blocks {
switch block.Type {
case "ipv4":
rule, blockDiags := decodeRuleBlock(block)
config.RulesV4 = append(config.RulesV4, rule)
diags = append(diags, blockDiags...)
case "ipv6":
rule, blockDiags := decodeRuleBlock(block)
config.RulesV6 = append(config.RulesV6, rule)
diags = append(diags, blockDiags...)
default:
// Should never happen because the above cases should be exhaustive
// for all block type names in our schema.
continue
}
}
if diags.HasErrors() {
return Config{}, errors.Wrap(diags, "failed to decode hcl content")
}
return config, nil
}
func decodeRuleBlock(block *hcl.Block) (Rule, hcl.Diagnostics) {
content, diags := block.Body.Content(ruleBlockSchema)
if content == nil {
return Rule{}, diags
}
rule := Rule{
Name: block.Labels[0],
}
if attr, exists := content.Attributes["priority"]; exists {
attrDiags := gohcl.DecodeExpression(attr.Expr, nil, &rule.Priority)
diags = append(diags, attrDiags...)
}
if attr, exists := content.Attributes["tcp"]; exists {
attrDiags := gohcl.DecodeExpression(attr.Expr, nil, &rule.TCP)
diags = append(diags, attrDiags...)
}
if attr, exists := content.Attributes["udp"]; exists {
attrDiags := gohcl.DecodeExpression(attr.Expr, nil, &rule.UDP)
diags = append(diags, attrDiags...)
}
return rule, diags
}
func main() {
config, err := decode()
if err != nil {
log.Fatal(err)
}
litter.Dump(config)
}
参考资料
A small repo of sample HCL2 apps
https://github.com/sean-/hcl2tests
HCL Config Language Toolkit
https://hcl.readthedocs.io/en/latest/
Terraform is a tool for building, changing, and combining infrastructure safely and efficiently.
https://github.com/hashicorp/terraform/tree/master/configs
最后修改于 2019-03-28