使用 Golang 解析 terraform tfvars 文件

问题描述 投票:0回答:1

我正在尝试使用 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解析?

go terraform hcl
1个回答
0
投票

当使用

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 更简单的应用程序。)

© www.soinside.com 2019 - 2024. All rights reserved.