Add an "include" template function
This lets YAML files include others, often conditionally, based on things like the cloud target. For example, I am currently using this to define the overall cluster stack by doing things like: name: mu/cluster services: {{if eq .Target "aws"}} {{include "Mu-aws.yaml" | indent 4}} {{else}} ... {{end}}
This commit is contained in:
parent
1302fc8a47
commit
63ddbc6e7a
|
@ -4,6 +4,9 @@ package compiler
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
|
@ -11,13 +14,39 @@ import (
|
|||
"github.com/marapongo/mu/pkg/compiler/core"
|
||||
"github.com/marapongo/mu/pkg/diag"
|
||||
"github.com/marapongo/mu/pkg/encoding"
|
||||
"github.com/marapongo/mu/pkg/util"
|
||||
)
|
||||
|
||||
// RenderTemplates performs standard template substitution on the given buffer using the given properties object.
|
||||
// TODO[marapongo/mu#7]: render many templates at once so they can share code.
|
||||
// TODO[marapongo/mu#7]: support configuration sections, etc., that can also contain templates.
|
||||
func RenderTemplates(doc *diag.Document, ctx *core.Context) (*diag.Document, error) {
|
||||
r, err := newRenderer(doc, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now actually render the template, supplying the context object as the data argument.
|
||||
b := bytes.NewBuffer(nil)
|
||||
if err = r.T.Execute(b, ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf("==\n%v\n", b.String())
|
||||
|
||||
return &diag.Document{
|
||||
File: doc.File,
|
||||
Body: b.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type renderer struct {
|
||||
T *template.Template
|
||||
doc *diag.Document
|
||||
ctx *core.Context
|
||||
}
|
||||
|
||||
func newRenderer(doc *diag.Document, ctx *core.Context) (*renderer, error) {
|
||||
r := &renderer{doc: doc, ctx: ctx}
|
||||
|
||||
t := template.New(doc.File)
|
||||
|
||||
// We will issue errors if the template tries to use a key that doesn't exist.
|
||||
|
@ -25,40 +54,57 @@ func RenderTemplates(doc *diag.Document, ctx *core.Context) (*diag.Document, err
|
|||
t.Option("missingkey=error")
|
||||
|
||||
// Add a stock set of helper functions to the template.
|
||||
t = t.Funcs(standardTemplateFuncs())
|
||||
t = t.Funcs(r.standardTemplateFuncs())
|
||||
|
||||
// Now actually render the template, supplying the context object as the data argument.
|
||||
// Parse up the resulting template from the provided document.
|
||||
var err error
|
||||
t, err = t.Parse(string(doc.Body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := bytes.NewBuffer(nil)
|
||||
if err = t.Execute(b, ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &diag.Document{
|
||||
File: doc.File,
|
||||
Body: b.Bytes(),
|
||||
}, nil
|
||||
|
||||
r.T = t
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func standardTemplateFuncs() template.FuncMap {
|
||||
// standardTemplateFuncs returns a new FuncMap containing all of the functions available to templates. It is a
|
||||
// member function of renderer because it closes over its state and may use it recursively.
|
||||
func (r *renderer) standardTemplateFuncs() template.FuncMap {
|
||||
// Use the Sprig library to seed our map with a lot of useful functions.
|
||||
// TODO[marapongo/mu#7]: audit these and add them one-by-one, so any changes are intentional. There also may be
|
||||
// some that we don't actually want to offer.
|
||||
funcs := sprig.TxtFuncMap()
|
||||
|
||||
// Add functions to unmarshal structures into their JSON/YAML textual equivalents.
|
||||
funcs["json"] = func(v interface{}) string {
|
||||
res, err := encoding.JSON.Marshal(v)
|
||||
util.AssertMF(err == nil, "Unexpected JSON marshaling error: %v", err)
|
||||
return string(res)
|
||||
// Include textually includes the given document, also expanding templates.
|
||||
funcs["include"] = func(name string) (string, error) {
|
||||
// Attempt to load the target file so that we may expand templates within it.
|
||||
dir := filepath.Dir(r.doc.File)
|
||||
path := filepath.Join(dir, name)
|
||||
raw, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Now perform the template expansion.
|
||||
b := bytes.NewBuffer(nil)
|
||||
u, err := r.T.Parse(string(raw))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := u.Execute(b, r.ctx); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
funcs["yaml"] = func(v interface{}) string {
|
||||
|
||||
// Add functions to unmarshal structures into their JSON/YAML textual equivalents.
|
||||
funcs["json"] = func(v interface{}) (string, error) {
|
||||
res, err := encoding.JSON.Marshal(v)
|
||||
return string(res), err
|
||||
}
|
||||
funcs["yaml"] = func(v interface{}) (string, error) {
|
||||
res, err := encoding.YAML.Marshal(v)
|
||||
util.AssertMF(err == nil, "Unexpected YAML marshaling error: %v", err)
|
||||
return string(res)
|
||||
return string(res), err
|
||||
}
|
||||
|
||||
return funcs
|
||||
|
|
Loading…
Reference in a new issue