tvl-depot/templater/templater.go
Vincent Ambo 25f2a1616c feat template: Add additional template functions
This adds the Go template functions from [sprig][] as well as a custom `json`
function that can interpolate any data as a JSON object - very useful for adding
arrays of data in JSON format into a variable:

```
certificateDomains:
  - oslo.pub
  - tazj.in

annotations:
  acme/certificate: {{ .certificateDomains | json }}

annotations:
  acme/certificate: ["oslo.pub", "tazj.in"]
```

[sprig]: https://godoc.org/github.com/Masterminds/sprig
2017-02-08 14:40:45 +01:00

143 lines
2.9 KiB
Go

package templater
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"text/template"
"github.com/Masterminds/sprig"
"github.com/polydawn/meep"
"github.com/tazjin/kontemplate/context"
)
// Error that is caused by non-existent template files being specified
type TemplateNotFoundError struct {
meep.AllTraits
Name string
}
// Error that is caused during templating, e.g. required value being absent or invalid template format
type TemplatingError struct {
meep.AllTraits
}
func LoadAndPrepareTemplates(limit *[]string, c *context.Context) (output []string, err error) {
for _, rs := range c.ResourceSets {
if resourceSetIncluded(limit, &rs.Name) {
err = processResourceSet(c, &rs, &output)
if err != nil {
return
}
}
}
return
}
func resourceSetIncluded(limit *[]string, resourceSetName *string) bool {
if len(*limit) == 0 {
return true
}
for _, name := range *limit {
if name == *resourceSetName {
return true
}
}
return false
}
func processResourceSet(c *context.Context, rs *context.ResourceSet, output *[]string) error {
fmt.Fprintf(os.Stderr, "Loading resources for %s\n", rs.Name)
rp := path.Join(c.BaseDir, rs.Name)
files, err := ioutil.ReadDir(rp)
err = processFiles(c, rs, rp, files, output)
if err != nil {
return meep.New(
&TemplateNotFoundError{Name: rs.Name},
meep.Cause(err),
)
}
return nil
}
func processFiles(c *context.Context, rs *context.ResourceSet, rp string, files []os.FileInfo, output *[]string) error {
for _, file := range files {
if !file.IsDir() && isResourceFile(file) {
p := path.Join(rp, file.Name())
o, err := templateFile(c, rs, p)
if err != nil {
return err
}
*output = append(*output, o)
}
}
return nil
}
func templateFile(c *context.Context, rs *context.ResourceSet, filename string) (string, error) {
tpl, err := template.New(path.Base(filename)).Funcs(templateFuncs()).ParseFiles(filename)
if err != nil {
return "", meep.New(
&TemplateNotFoundError{Name: filename},
meep.Cause(err),
)
}
var b bytes.Buffer
// Guard against empty map before merging keys
if rs.Values == nil {
rs.Values = make(map[string]interface{}, 0)
}
// Merge global and resourceset-specific values (don't override from global)
for k, v := range c.Global {
if _, ok := rs.Values[k]; !ok {
rs.Values[k] = v
}
}
err = tpl.Execute(&b, rs.Values)
if err != nil {
return "", meep.New(
&TemplatingError{},
meep.Cause(err),
)
}
return b.String(), nil
}
func templateFuncs() template.FuncMap {
m := sprig.TxtFuncMap()
m["json"] = func(data interface{}) string {
b, _ := json.Marshal(data)
return string(b)
}
return m
}
// Checks whether a file is a resource file (i.e. is YAML or JSON)
func isResourceFile(f os.FileInfo) bool {
return strings.HasSuffix(f.Name(), "yaml") ||
strings.HasSuffix(f.Name(), "yml") ||
strings.HasSuffix(f.Name(), "json")
}