Skip to content

Instantly share code, notes, and snippets.

@dcwangmit01
Last active March 2, 2024 14:54
Show Gist options
  • Save dcwangmit01/e9e58dd123e0a8eb48c2f4e8ffa16d5a to your computer and use it in GitHub Desktop.
Save dcwangmit01/e9e58dd123e0a8eb48c2f4e8ffa16d5a to your computer and use it in GitHub Desktop.
Self-templating with golang text/template and gomplate
/*
Demo program illustrating usage of gomplate as a library.
This program will accept an input file and run it through the templating
engine as both the values dictionary as well as the template file. It will
repeat the process until the templating result stops changing, and print
the result to STDOUT.
This program add the gomplate datasources and functions to the golang
text/template engine. This requires the input to be have the gomplate
datasources and datasourceheaders to be set.
# Create the template-and-values.yaml file
cat > template-and-values.yaml << EOF
gomplate:
datasources:
- "file_obj=./test-person.json"
- "http_obj=https://httpbin.org/get"
datasourceheaders: []
values:
a: foo
b: '[[ .values.a ]]bar' # foobar
c: '[[ .values.b ]]baz' # foobarbaz
d: 'Http datasource: [[ (ds "http_obj").headers.Host ]]'
e: 'File datasource: [[ (ds "file_obj").name ]]'
f: 'Func Test: [[ net.LookupIP "example.com" ]]'
EOF
# Create the test-person.json file
cat > test-person.json << EOF
{
"name": "Dave"
}
EOF
# Compile the program
go build -o demo main.go
# Run the program
./demo template-and-values.yaml
*/
package main
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"text/template"
"github.com/hairyhenderson/gomplate"
"github.com/hairyhenderson/gomplate/data"
"github.com/ghodss/yaml"
)
type Values map[string]interface{}
func main() {
fileName := os.Args[1]
data, err := ioutil.ReadFile(fileName)
if err != nil {
fmt.Printf("Failed to read fileName %v", fileName)
}
contents := string(data)
renderedContents, err := selfRender(contents, true)
if err != nil {
fmt.Printf("Failed to selfRender %v", contents)
}
fmt.Print(renderedContents)
}
func convertInterfaceListToStringList(l []interface{}) ([]string) {
s := make([]string, len(l))
for i, v := range l {
s[i] = fmt.Sprint(v)
}
return s
}
func selfRender(templateValuesStr string, enableGomplate bool) (string, error) {
/*
This function will accept an input string and run it through the
templating engine as both the values dictionary as well as the
template string. It will repeat the process until the templating
result stops changing.
*/
lastRender := templateValuesStr
for i := 0; i < 10; i++ {
// Unmarshal the file as a values dict
vals := Values{}
err := yaml.Unmarshal([]byte(templateValuesStr), &vals)
if err != nil { panic(err) }
tmpl := template.New("SelfTemplate")
tmpl.Delims("[[", "]]")
if enableGomplate {
// Read the defined datasources and datasourcehaders
// from the values dict
dataSources := []string{}
dataSourceHeaders := []string{}
if _, ok := vals["gomplate"]; ok {
tmp := vals["gomplate"].(map[string]interface{})
if _, ok := tmp["datasources"]; ok {
dataSources = convertInterfaceListToStringList(
tmp["datasources"].([]interface{}))
}
if _, ok := tmp["datasources"]; ok {
dataSourceHeaders = convertInterfaceListToStringList(
tmp["datasourceheaders"].([]interface{}))
}
}
// Access gomplate datasources and function library
d, err := data.NewData(dataSources, dataSourceHeaders)
if err != nil { panic(err) }
// Configure go/text/template to use gomplate
// datasources and function library.
tmpl.Option("missingkey=error")
tmpl.Funcs(gomplate.Funcs(d))
}
// Run the the file through the tempating engine as both values
// file and template file
tmpl.Parse(string(templateValuesStr))
if err != nil { panic(err) }
out := new(bytes.Buffer)
err = tmpl.Execute(out, vals)
if err != nil { panic(err) }
newRender := out.String()
if lastRender == newRender {
return newRender, nil // self-templating succeeded
} else {
lastRender = newRender
templateValuesStr = newRender
}
}
return templateValuesStr, errors.New("Self-templating failed")
}
$ ./demo template-and-values.yaml
gomplate:
datasources:
- "file_obj=./test-person.json"
- "http_obj=https://httpbin.org/get"
datasourceheaders: []
values:
a: foo
b: 'foobar' # foobar
c: 'foobarbaz' # foobarbaz
d: 'Http datasource: httpbin.org'
e: 'File datasource: Dave'
f: 'Func Test: 10.114.236.103'
gomplate:
datasources:
- "file_obj=./test-person.json"
- "http_obj=https://httpbin.org/get"
datasourceheaders: []
values:
a: foo
b: '[[ .values.a ]]bar' # foobar
c: '[[ .values.b ]]baz' # foobarbaz
d: 'Http datasource: [[ (ds "http_obj").headers.Host ]]'
e: 'File datasource: [[ (ds "file_obj").name ]]'
f: 'Func Test: [[ net.LookupIP "example.com" ]]'
{
"name": "Dave"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment