无法将 terraform variables.tf 文件读入可能进入程序

Unable to read terraform variables.tf files into may go program

我正在尝试编写一个读取 terraform variables.tf 并填充结构以供以后操作的 go 程序。但是,我在尝试“解析”文件时遇到错误。我希望有人能告诉我我做错了什么:

代码:

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "os"

    "github.com/hashicorp/hcl/v2"
    "github.com/hashicorp/hcl/v2/gohcl"
    "github.com/hashicorp/hcl/v2/hclsyntax"
)

type Config struct {
    Upstreams []*TfVariable `hcl:"variable,block"`
}

type TfVariable struct {
    Name string `hcl:",label"`
    // Default     string `hcl:"default,optional"`
    Type        string `hcl:"type"`
    Description string `hcl:"description,attr"`
    // validation block
    Sensitive bool `hcl:"sensitive,optional"`
}

func main() {
    readHCLFile("examples/string.tf")
}

// Exits program by sending error message to standard error and specified error code.
func abort(errorMessage string, exitcode int) {
    fmt.Fprintln(os.Stderr, errorMessage)
    os.Exit(exitcode)
}

func readHCLFile(filePath string) {
    content, err := ioutil.ReadFile(filePath)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("File contents: %s", content) // TODO: Remove me

    file, diags := hclsyntax.ParseConfig(content, filePath, hcl.Pos{Line: 1, Column: 1})
    if diags.HasErrors() {
        log.Fatal(fmt.Errorf("ParseConfig: %w", diags))
    }

    c := &Config{}
    diags = gohcl.DecodeBody(file.Body, nil, c)
    if diags.HasErrors() {
        log.Fatal(fmt.Errorf("DecodeBody: %w", diags))
    }

    fmt.Println(c) // TODO: Remove me
}

错误

File contents: variable "image_id" {
  type        = string
  description = "The id of the machine image (AMI) to use for the server."
  sensitive   = false
}

variable "other_id" {
  type        = string
  description = "The id of the machine image (AMI) to use for the server."
  sensitive   = true
}
2021/03/13 19:55:49 DecodeBody: examples/string.tf:2,17-23: Variables not allowed; Variables may not be used here., and 3 other diagnostic(s)
exit status 1

堆栈驱动程序 可悲的是 hcl1

Blog post我在引用

它看起来像是图书馆的 bug/feature,因为一旦您将 string 更改为 "string",例如

variable "image_id" {
  type        = string
  ...

variable "image_id" {
  type        = "string"
  ...

gohcl.DecodeBody 成功。

---更新---

所以,他们确实在 Terraform 中使用了这个包,但是他们 custom-parse configs, i.e., they don't use gohcl.DecodeBody. They also custom-treat type attributes by using hcl.ExprAsKeyword (compare with description). As you assumed, they do use a custom type 用于 type,但是使用自定义解析你不必这样做。

下面是一个工作示例:

package main

import (
    "fmt"
    "log"
    "os"

    "github.com/hashicorp/hcl/v2"
    "github.com/hashicorp/hcl/v2/gohcl"
    "github.com/hashicorp/hcl/v2/hclsyntax"
)

var (
    configFileSchema = &hcl.BodySchema{
        Blocks: []hcl.BlockHeaderSchema{
            {
                Type:       "variable",
                LabelNames: []string{"name"},
            },
        },
    }

    variableBlockSchema = &hcl.BodySchema{
        Attributes: []hcl.AttributeSchema{
            {
                Name: "description",
            },
            {
                Name: "type",
            },
            {
                Name: "sensitive",
            },
        },
    }
)

type Config struct {
    Variables []*Variable
}

type Variable struct {
    Name        string
    Description string
    Type        string
    Sensitive   bool
}

func main() {
    config := configFromFile("examples/string.tf")
    for _, v := range config.Variables {
        fmt.Printf("%+v\n", v)
    }
}

func configFromFile(filePath string) *Config {
    content, err := os.ReadFile(filePath) // go 1.16
    if err != nil {
        log.Fatal(err)
    }

    file, diags := hclsyntax.ParseConfig(content, filePath, hcl.Pos{Line: 1, Column: 1})
    if diags.HasErrors() {
        log.Fatal("ParseConfig", diags)
    }

    bodyCont, diags := file.Body.Content(configFileSchema)
    if diags.HasErrors() {
        log.Fatal("file content", diags)
    }

    res := &Config{}

    for _, block := range bodyCont.Blocks {
        v := &Variable{
            Name: block.Labels[0],
        }

        blockCont, diags := block.Body.Content(variableBlockSchema)
        if diags.HasErrors() {
            log.Fatal("block content", diags)
        }

        if attr, exists := blockCont.Attributes["description"]; exists {
            diags := gohcl.DecodeExpression(attr.Expr, nil, &v.Description)
            if diags.HasErrors() {
                log.Fatal("description attr", diags)
            }
        }

        if attr, exists := blockCont.Attributes["sensitive"]; exists {
            diags := gohcl.DecodeExpression(attr.Expr, nil, &v.Sensitive)
            if diags.HasErrors() {
                log.Fatal("sensitive attr", diags)
            }
        }

        if attr, exists := blockCont.Attributes["type"]; exists {
            v.Type = hcl.ExprAsKeyword(attr.Expr)
            if v.Type == "" {
                log.Fatal("type attr", "invalid value")
            }
        }

        res.Variables = append(res.Variables, v)
    }
    return res
}

为完整性添加,example/string.tf:

variable "image_id" {
  type        = string
  description = "The id of the machine image (AMI) to use for the server."
  sensitive   = false
}

variable "other_id" {
  type        = string
  description = "The id of the machine image (AMI) to use for the server."
  sensitive   = true
}

由于 Terraform 语言广泛使用各种 HCL 功能,这些功能需要使用低级 HCL API 进行自定义编程,因此 Terraform 团队维护了一个足以理解 Terraform 语言的 Go 库 terraform-config-inspect提取关于顶级对象的静态元数据,包括变量。它还涉及 Terraform 允许在任何 .tf.tf.json 文件中与其他声明交错的变量定义这一事实;将它们放在 variables.tf 中只是一种约定。

例如:

mod, diags := tfconfig.LoadModule("examples")
if diags.HasErrors() {
    log.Fatalf(diags.Error())
}
for _, variable := range mod.Variables {
    fmt.Printf("%#v\n", variable)
}

此库与 Terraform Registry to produce the documentation about module input variables 使用的代码相同,因此它支持 Terraform Registry 支持的所有 Terraform 语言版本(在撰写本文时,回到 Terraform v0.10 语言,因为那是第一个可以从注册表安装模块的版本)并支持 HCL 本机语法和 Terraform 语言的 JSON 表示。