Skip to content
This repository has been archived by the owner on Oct 3, 2019. It is now read-only.

Example usage of custom defined function #5

Closed
fatih opened this issue Jan 15, 2018 · 1 comment
Closed

Example usage of custom defined function #5

fatih opened this issue Jan 15, 2018 · 1 comment

Comments

@fatih
Copy link
Contributor

fatih commented Jan 15, 2018

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

@fatih
Copy link
Contributor Author

fatih commented Jan 20, 2018

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"
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant