template.go

v0.6.0
Doc Versions Source
1
package steps
2
3
import (
4
	"bytes"
5
	"text/template"
6
7
	"go.bigb.es/auxilia/culpa"
8
	"go.bigb.es/auxilia/legatus"
9
)
10
11
// Template renders a text/template into a remote file. The template data is the
12
// merge of Host.Vars and the FlowCtx param bag (params win on key collision).
13
// Once rendered, it follows File semantics for idempotency and upload.
14
type Template struct {
15
	Path  string
16
	Text  string
17
	Mode  legatus.FileMode
18
	Owner string
19
}
20
21
// Name returns the step name.
22
func (t Template) Name() string { return "template:" + t.Path }
23
24
// Check renders the template and reports a change is needed unless the remote
25
// file already matches the rendered content and mode.
26
func (t Template) Check(fc *legatus.FlowCtx) (bool, error) {
27
	rendered, err := t.render(fc)
28
	if err != nil {
29
		return false, err
30
	}
31
	match, err := fileMatches(fc, t.Path, rendered, t.Mode)
32
	if err != nil {
33
		return false, err
34
	}
35
	return !match, nil
36
}
37
38
// Apply renders the template and uploads it.
39
func (t Template) Apply(fc *legatus.FlowCtx) (legatus.Outcome, error) {
40
	rendered, err := t.render(fc)
41
	if err != nil {
42
		return legatus.Outcome{}, err
43
	}
44
	if err := putFile(fc, t.Path, rendered, t.Mode, t.Owner, false); err != nil {
45
		return legatus.Outcome{}, err
46
	}
47
	return legatus.Outcome{Changed: true, Message: "rendered " + t.Path}, nil
48
}
49
50
// render executes the template against Host.Vars as the dot data, with FlowCtx
51
// params reachable through the "param" function: {{param "Key"}}. Params are
52
// not folded into the dot map because the core FlowCtx exposes no public
53
// enumeration of its private param bag (see report); a per-key lookup is the
54
// faithful, non-fabricating way to read forward-passed values.
55
func (t Template) render(fc *legatus.FlowCtx) ([]byte, error) {
56
	data := map[string]any{}
57
	for k, v := range fc.Host().Vars {
58
		data[k] = v
59
	}
60
61
	funcs := template.FuncMap{
62
		"param": func(key string) (any, error) {
63
			v, ok := fc.Get(key)
64
			if !ok {
65
				return nil, culpa.WithCode(culpa.Errorf("template %s references missing param %q", t.Path, key), "TEMPLATE_PARAM")
66
			}
67
			return v, nil
68
		},
69
	}
70
71
	tmpl, err := template.New(t.Path).Option("missingkey=error").Funcs(funcs).Parse(t.Text)
72
	if err != nil {
73
		return nil, culpa.WithCode(culpa.Wrapf(err, "parse template %s", t.Path), "TEMPLATE_PARSE")
74
	}
75
	var buf bytes.Buffer
76
	if err := tmpl.Execute(&buf, data); err != nil {
77
		return nil, culpa.WithCode(culpa.Wrapf(err, "render template %s", t.Path), "TEMPLATE_RENDER")
78
	}
79
	return buf.Bytes(), nil
80
}
81

Source Files