How do I provide a schema in Go for HCL2

This question is specific to HCL2 in Golang, not Packer. I figured it’s better to ask here than in a GitHub issue.

Ultimately, I’m trying to convert an HCL config into JSON for a third party app (slackhq/nebula). I can read an HCL file but am stuck on how to define the schema in Go. I ran across hcldec and it does what I want but from the command line. How do I specify the “.hcldec” in Go?

Right now I’m getting this error. It’s my understanding the types are using go-cty. I’m not a Go expert so I’m lost as to how to specify the types or the schema. Everything I found uses simple “name=value” without going into deeper concepts like blocks or nested blocks, much less how to read blocks in Go. The gohcl example writes Go structs to HCL but I’m looking for the reverse. Any feedback is appreciated.

panic: unsuitable DecodeExpression target: no cty.Type for main.Lighthouse (no cty field tags)

type Config struct {
	Pki           map[string]string `hcl:"pki"`
	StaticHostMap []string          `hcl:"static_host_map"`
	Lighthouse    *Lighthouse       `hcl:"lighthouse,optional"`
}

type Lighthouse struct {
	AmLighthouse string   `hcl:"am_lighthouse"`
	Interval     int      `hcl:"interval"`
	Hosts        []string `hcl:"hosts"`
}
lighthouse = {
  am_lighthouse = false
  interval = 60
  hosts = [
    "192.168.1.1"
  ]
}

I think I found the answer. Packer’s hcl2template has many type declaration files. The simplest is probably parser.go which has a variable configSchema as a block of type hcl.BodySchema. This is a real life example of what I’m trying to do.

For future travelers, here’s a complete example.

package main

import (
	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/gohcl"
	"github.com/hashicorp/hcl/v2/hclparse"
	"log"
)

type Config struct {
	Lighthouse Lighthouse `hcl:"lighthouse"`
}

type Lighthouse struct {
	AmLighthouse string   `hcl:"am_lighthouse"`
	Interval     int      `hcl:"interval"`
	Hosts        []string `hcl:"hosts"`
}

var configSchema = &hcl.BodySchema{
	Blocks: []hcl.BlockHeaderSchema{
		{
			Type: "lighthouse",
		},
	},
}

var lighthouseBlockSchema = &hcl.BodySchema{
	Attributes: []hcl.AttributeSchema{
		{Name: "am_lighthouse"},
		{Name: "interval"},
		{Name: "hosts"},
	},
}

func main() {
	var filename = "example.hcl"

	parser := hclparse.NewParser()
	file, parseDiags := parser.ParseHCLFile(filename)

	if parseDiags.HasErrors() {
		log.Fatalf("parse error: %v", parseDiags)
	}

	bodyCont, confDiags := file.Body.Content(configSchema)
	if confDiags.HasErrors() {
		log.Fatalf("config error: %v", confDiags)
	}

	var c = &Config{}
	blockLh, blockDiags := bodyCont.Blocks[0].Body.Content(lighthouseBlockSchema)
	if blockDiags.HasErrors() {
		log.Fatalf("block content error: %v", blockDiags)
	}

	if attr, exists := blockLh.Attributes["am_lighthouse"]; exists {
		decodeDiags := gohcl.DecodeExpression(attr.Expr, nil, &c.Lighthouse.AmLighthouse)
		if decodeDiags.HasErrors() {
			log.Fatalf("decode am_lighthouse attr error: %v", decodeDiags)
		}
		log.Printf("am_lighthouse: %v", c.Lighthouse.AmLighthouse)
	}

	if attr, exists := blockLh.Attributes["interval"]; exists {
		decodeDiags := gohcl.DecodeExpression(attr.Expr, nil, &c.Lighthouse.Interval)
		if decodeDiags.HasErrors() {
			log.Fatalf("decode interval attr error: %v", decodeDiags)
		}
		log.Printf("interval: %v", c.Lighthouse.Interval)
	}

	if attr, exists := blockLh.Attributes["hosts"]; exists {
		decodeDiags := gohcl.DecodeExpression(attr.Expr, nil, &c.Lighthouse.Hosts)
		if decodeDiags.HasErrors() {
			log.Fatalf("decode hosts attr error: %v", decodeDiags)
		}
		log.Printf("hosts: %v", c.Lighthouse.Hosts)
	}

}

example.hcl

lighthouse {
  am_lighthouse = false
  interval = 60
  hosts = [
    "192.168.1.1"
  ]
}