我正在尝试使用 golang 动态操作 tfvars 文件。
这是我的代码
package main
import (
"fmt"
"io"
"log"
"os"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/hclwrite"
)
type RawDatabase struct {
Name string `hcl:"name"`
BusinessVerticalID string `hcl:"business_vertical_id"`
Readers []string `hcl:"readers"`
Contributors []string `hcl:"contributors"`
}
type Config struct {
Environment string `hcl:"environment"`
BusinessEntity *string `hcl:"business_entity"`
EntitlementLookup map[string]string `hcl:"entitlement_lookup"`
RawDatabases []RawDatabase `hcl:"raw_databases"`
}
func readFile(filePath string) ([]byte, error) {
// Open the file for reading
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
// Read the file content into a byte slice
content, err := io.ReadAll(file)
if err != nil {
return nil, err
}
return content, nil
}
func writeFile(data []byte, filePath string) ( error) {
// Open the file for reading
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return err
}
defer file.Close()
// Read the file content into a byte slice
_, err = file.Write(data)
if err != nil {
return err
}
return nil
}
func main() {
// Read the tfvar file content
content, err := readFile("demo.tfvars")
if err != nil {
log.Fatalf("Error reading tfvars file: %v", err)
}
// Parse tfvars content
parsedObject, diags := hclsyntax.ParseConfig(content, "", hcl.Pos{})
if diags.HasErrors() {
log.Fatalf("Failed to parse tfvars content: %s", diags)
}
// Print parsedObject.Body to debug
fmt.Println("Parsed Body:", parsedObject.Body)
// Decode HCL into Go struct
var config Config
diags = gohcl.DecodeBody(parsedObject.Body, nil, &config)
if diags.HasErrors() {
log.Fatalf("Failed to decode tfvars content: %s", diags)
}
// Print Go struct
fmt.Printf("%+v\n", config)
// Convert Go struct back to HCL
f := hclwrite.NewEmptyFile()
gohcl.EncodeIntoBody(&config, f.Body())
// Write HCL content to a file
err = writeFile(f.Bytes(), "../../../terraform/project1/dev_out.tfvars")
if err != nil {
log.Fatalf("Failed to write HCL to file: %v", err)
}
}
并且
demo.tfvars
文件内容是
environment = "dev"
entitlement_lookup = {
"obj1" = "obj_id1",
"obj2" = "obj_id2",
}
raw_databases = [
{
name = "db1"
business_vertical_id = "fin"
readers = []
contributors = ["obj1", "obj2"]
},
{
name = "db2"
business_vertical_id = "fin"
readers = []
contributors = ["obj2"]
},
{
name = "db3"
business_vertical_id = "fin"
readers = []
contributors = ["obj1"]
}
]
如果我尝试在没有 raw_database 的情况下使用上述代码,我的代码工作正常。
帮我解决错误
panic: unsuitable DecodeExpression target: no cty.Type for main.RawDatabase (no cty field tags)
goroutine 1 [running]:
github.com/hashicorp/hcl/v2/gohcl.DecodeExpression({0x7fdab078cc68, 0xc0000fa8c0}, 0x5f54e0?, {0xc0000bbc80, 0xc0000bb9a0})
/go/pkg/mod/github.com/hashicorp/hcl/[email protected]/gohcl/decode.go:296 +0x7f4
github.com/hashicorp/hcl/v2/gohcl.decodeBodyToStruct({0x68cb58, 0xc000136420}, 0x0, {0x615b80?, 0xc0000bb980?, 0xc00019fe00?})
/go/pkg/mod/github.com/hashicorp/hcl/[email protected]/gohcl/decode.go:127 +0xd38
github.com/hashicorp/hcl/v2/gohcl.decodeBodyToValue({0x68cb58, 0xc000136420}, 0x0, {0x615b80?, 0xc0000bb980?, 0xc00019fe78?})
/go/pkg/mod/github.com/hashicorp/hcl/[email protected]/gohcl/decode.go:46 +0xba
github.com/hashicorp/hcl/v2/gohcl.DecodeBody({0x68cb58, 0xc000136420}, 0x0, {0x5f2060?, 0xc0000bb980?})
/go/pkg/mod/github.com/hashicorp/hcl/[email protected]/gohcl/decode.go:39 +0xc5
main.main()
/workspaces/cdassp/go/cmd/go_hcl/main.go:106 +0x1bd
exit status 2
我已经在下面验证了
双重检查标记位置:确保每个 hcl:"..." 标记直接放置在相应的结构字段上方。不正确的放置可能会导致错误。
验证标签语法:确保标签格式正确:hcl:"key_name"。键名称应与该字段的 HCL 配置文件中使用的确切名称匹配。
这是否意味着struct对象不能用HCL解析?
当使用
gohcl
抽象使用 Go struct 标签声明 HCL 架构时,HCL 标签只能表示可以映射到 HCL 的“主体架构”模型的概念。这意味着:
HCL 本身并不对属性类型或值进行建模。相反,它将其委托给名为
cty
的上游库。您可以看到,在 HCL 的低级 API 中,hcl.Expression
的 Value
方法返回 cty.Value
,而不是 HCL 特定的类型。 (披露:我是 cty
的主要作者和维护者。)
为了支持
gohcl
抽象,HCL 将属性值解码委托给 cty
的相应包 gocty
,该包有自己的规则将 cty.Value
映射到“正常”Go 类型,包括其自己的结构标记用于描述对象类型。
这意味着,如果您想将对象类型值解码为 Go 结构类型的实例,那么您需要使用
gocty
的结构标签声明该结构类型,而不是 gohcl
的结构标签。 gohcl
的结构标签仅用于解码HCL自己的概念:属性和嵌套块。
下面这对类型应该可以达到你想要的效果:
type RawDatabase struct {
Name string `cty:"name"`
BusinessVerticalID string `cty:"business_vertical_id"`
Readers []string `cty:"readers"`
Contributors []string `cty:"contributors"`
}
type Config struct {
Environment string `hcl:"environment"`
BusinessEntity *string `hcl:"business_entity"`
EntitlementLookup map[string]string `hcl:"entitlement_lookup"`
RawDatabases []RawDatabase `hcl:"raw_databases"`
}
请注意,
RawDatabase
现在具有cty:
结构标签,而不是hcl:
结构标签。 Config
中的标签告诉 gohcl
在其生成的 hcl.BodySchema
对象中请求哪些属性。 RawDatabases
的类型以及 RawDatabase
中的字段标签,告诉 gocty
期望一个对象列表,每个对象都具有您指定的四个属性。
Terraform 本身不使用
gohcl
抽象来实现其.tfvars
格式。相反,它直接与低级 HCL API 配合使用。
Terraform 对此类文件的解释大致如下:
file, diags := hclsyntax.ParseConfig([]byte(src), "filename.tfvars", hcl.InitialPos)
if diags.HasErrors() {
// (handle the errors)
}
attrs, diags := file.Body.JustAttributes()
if diags.HasErrors() {
// (handle the errors)
}
vals := make(map[string]cty.Value, len(attrs))
for name, attr := range attrs {
vals[name], diags = attr.Expr.Value(nil)
if diags.HasErrors() {
// (handle the errors)
}
}
vals
中的结果是一个映射,其中每个定义的变量都有一个元素,其中每个变量都表示为cty.Value
。
Terraform 永远不需要将这些值转换为 Go 字符串、切片或结构类型,因为它使用
cty.Value
API 完成所有工作,因为它代表了 Terraform 语言类型系统。
但是,如果您需要出于自己的目的这样做,那么您仍然可以根据需要将
gocty
与值一起使用,将每个值传递到具有适当目标类型的 gocty.FromCtyValue
中。
但是,如果您的目标允许对文件的解释比 Terraform 更严格,那么使用
gohcl
和 gocty
的原始方法以及我上面建议的修改是一个合理的捷径。我提到这个其他细节只是为了避免给人一种误导性的印象,即 Terraform 的 .tfvars
格式是使用 gohcl
帮助器解码的。 (gohcl
适用于需求比 Terraform 更简单的应用程序。)