Closed fatih closed 6 years ago
Here is an example of doing it one way, if anyone looks how to implement it:
package main
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/hashicorp/hcl2/gohcl"
"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcldec"
"github.com/hashicorp/hcl2/hclparse"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/function/stdlib"
ctyjson "github.com/zclconf/go-cty/cty/json"
)
func main() {
var src = `
variable "city" {
default = "Ankara"
}
variable "year" {
default = "2000"
}
resource "fatih" "arslan" {
result = "Address: ${upper(var.city)}, Age: ${age(var.year)}"
}
`
parser := hclparse.NewParser()
file, diags := parser.ParseHCL([]byte(src), "demo.hcl")
if len(diags) != 0 {
for _, diag := range diags {
fmt.Printf("- %s\n", diag)
}
return
}
body := file.Body
type Variable struct {
Name string `hcl:"name,label"`
Default hcl.Attributes `hcl:"default,remain"`
}
type Resource struct {
Type string `hcl:"type,label"`
Name string `hcl:"name,label"`
Config hcl.Body `hcl:",remain"`
}
type Root struct {
Variables []*Variable `hcl:"variable,block"`
Resources []*Resource `hcl:"resource,block"`
}
var root Root
diags = gohcl.DecodeBody(body, nil, &root)
if len(diags) != 0 {
for _, diag := range diags {
fmt.Printf("decoding - %s\n", diag)
}
return
}
variables := map[string]cty.Value{}
for _, v := range root.Variables {
if len(v.Default) == 0 {
continue
}
val, diags := v.Default["default"].Expr.Value(nil)
if len(diags) != 0 {
for _, diag := range diags {
fmt.Printf("decoding - %s\n", diag)
}
return
}
variables[v.Name] = val
}
evalContext := &hcl.EvalContext{
Variables: map[string]cty.Value{
"var": cty.ObjectVal(variables),
},
Functions: map[string]function.Function{
"upper": stdlib.UpperFunc,
"age": age(),
},
}
// just for resource address and bar
spec := &hcldec.ObjectSpec{
"result": &hcldec.AttrSpec{
Name: "result",
Required: true,
Type: cty.String,
},
}
cfg, diags := hcldec.Decode(root.Resources[0].Config, spec, evalContext)
if len(diags) != 0 {
for _, diag := range diags {
fmt.Printf("- %s\n", diag)
}
return
}
wantCfg := cty.ObjectVal(map[string]cty.Value{
"result": cty.StringVal("Address: ANKARA, Age: 18"),
})
if !cfg.RawEquals(wantCfg) {
log.Fatalf("wrong config\ngot: %#v\nwant: %#v", cfg, wantCfg)
}
out, err := json.MarshalIndent(ctyjson.SimpleJSONValue{cfg}, "", " ")
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(out))
}
// age() is a custom function that returns back the age from the birth year
func age() function.Function {
return function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "year",
Type: cty.Number,
},
},
Type: function.StaticReturnType(cty.Number),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
in := args[0].AsBigFloat()
year, _ := in.Int64()
age := int64(time.Now().Year()) - year
return cty.NumberIntVal(age), nil
},
})
}
Prints:
{
"result": "Address: ANKARA, Age: 18"
}
Hi,
I see that this project will replace HCL and HIL going forward. Are there any example on how to use it with custom defined function (like how Terraform injects into the HIL's eval context). The README and doc is highly technical written and seems like until I read the source code it's not easily discoverable.
Thanks