WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 068c56d

Browse files
committed
make templating best practices
Signed-off-by: Kit Patella <[email protected]>
1 parent aafc9fc commit 068c56d

File tree

2 files changed

+391
-95
lines changed

2 files changed

+391
-95
lines changed
Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
---
2+
title: Templating
3+
sidebar:
4+
order: 11
5+
---
6+
7+
This guide provides best practices for using [templating](/ref/templating/) in your Zarf packages.
8+
9+
:::caution
10+
11+
Templating with Package Values is currently an **alpha feature** and requires the `--features="values=true"` flag when creating, deploying, inspecting, or removing packages.
12+
13+
:::
14+
15+
## Use Pipelines for Readability
16+
17+
Chain functions together using the pipe operator (`|`) for clarity:
18+
19+
```yaml
20+
# ✅ GOOD: Clear transformation pipeline
21+
name: {{ .Values.app.name | lower | kebabcase | trunc 50 }}
22+
23+
# ❌ AVOID: Nested function calls
24+
name: {{ trunc 50 (kebabcase (lower .Values.app.name)) }}
25+
```
26+
27+
## Always Quote String Values
28+
29+
Use the `quote` function to ensure string values are properly quoted in YAML:
30+
31+
```yaml
32+
# ✅ GOOD: Properly quoted
33+
environment: {{ .Values.app.environment | quote }}
34+
image: {{ .Values.app.image | quote }}
35+
36+
# ❌ RISKY: Can cause YAML parsing issues
37+
environment: {{ .Values.app.environment }}
38+
```
39+
40+
**Why this matters:**
41+
- Prevents YAML parsing errors with special characters
42+
- Handles values like `"yes"`, `"no"`, `"true"`, `"false"` correctly
43+
- Protects against values that look like numbers
44+
45+
**Example of the problem:**
46+
47+
```yaml
48+
# Without quote, this breaks YAML parsing
49+
apiVersion: {{ .Values.version }} # If version is "1.0", breaks
50+
apiVersion: {{ .Values.version | quote }} # "1.0" works correctly
51+
```
52+
53+
## Provide Defaults for Optional Values
54+
55+
Use the `default` function to provide fallback values:
56+
57+
```yaml
58+
# ✅ GOOD: Safe with defaults
59+
replicas: {{ .Values.app.replicas | default 3 }}
60+
namespace: {{ .Values.app.namespace | default "default" | quote }}
61+
logLevel: {{ .Values.app.logLevel | default "info" | quote }}
62+
63+
# ❌ RISKY: Fails if value is missing
64+
replicas: {{ .Values.app.replicas }}
65+
```
66+
67+
**When to use defaults:**
68+
- Optional configuration values
69+
- Values that have sensible fallbacks
70+
- Any value that might not be provided by users
71+
72+
**Combine with other functions:**
73+
74+
```yaml
75+
# Default, then transform
76+
environment: {{ .Values.app.environment | default "production" | upper | quote }}
77+
```
78+
79+
## Control Whitespace
80+
81+
Use whitespace control operators (`-`) to manage output formatting:
82+
83+
```yaml
84+
# ✅ GOOD: Clean output, no extra blank lines
85+
{{- if .Values.app.debug }}
86+
debug: true
87+
{{- end }}
88+
89+
# ❌ PRODUCES UNWANTED BLANK LINES
90+
{{ if .Values.app.debug }}
91+
debug: true
92+
{{ end }}
93+
```
94+
95+
**Whitespace control operators:**
96+
- `{{-` removes whitespace before the template
97+
- `-}}` removes whitespace after the template
98+
- `{{- -}}` removes whitespace on both sides
99+
100+
**Example output difference:**
101+
102+
```yaml
103+
# Without whitespace control:
104+
metadata:
105+
labels:
106+
107+
app: nginx
108+
109+
# With whitespace control:
110+
metadata:
111+
labels:
112+
app: nginx
113+
```
114+
115+
## Comment Complex Template Logic
116+
117+
Add comments to explain non-obvious template logic:
118+
119+
```yaml
120+
# ✅ GOOD: Explained logic
121+
# Calculate scaled replica count based on environment
122+
# Production gets 3x replicas, non-production gets 1x
123+
{{- $baseReplicas := .Values.app.replicas | default 1 -}}
124+
{{- $scaleFactor := .Values.app.production | ternary 3 1 -}}
125+
replicas: {{ mul $baseReplicas $scaleFactor }}
126+
127+
# ❌ UNCLEAR: What's happening here?
128+
{{- $baseReplicas := .Values.app.replicas | default 1 -}}
129+
{{- $scaleFactor := .Values.app.production | ternary 3 1 -}}
130+
replicas: {{ mul $baseReplicas $scaleFactor }}
131+
```
132+
133+
**What to comment:**
134+
- Complex conditionals
135+
- Non-obvious default values
136+
- Business logic encoded in templates
137+
138+
## Avoid Over-Templating
139+
140+
Keep templates simple and prefer data over complex logic:
141+
142+
```yaml
143+
# ✅ GOOD: Simple conditional
144+
{{- if .Values.monitoring.enabled }}
145+
annotations:
146+
prometheus.io/scrape: "true"
147+
{{- end }}
148+
149+
# ❌ AVOID: Complex nested logic
150+
{{- if and .Values.monitoring.enabled (or (eq .Values.app.environment "production") (eq .Values.app.environment "staging")) (not .Values.app.debug) }}
151+
annotations:
152+
prometheus.io/scrape: "true"
153+
prometheus.io/port: {{ .Values.monitoring.port | default 8080 | quote }}
154+
{{- end }}
155+
```
156+
157+
**Better approach for complex cases:**
158+
159+
Move logic to values files:
160+
161+
```yaml
162+
# values.yaml
163+
monitoring:
164+
enabled: true
165+
annotations:
166+
prometheus.io/scrape: "true"
167+
prometheus.io/port: "8080"
168+
```
169+
170+
```yaml
171+
# manifest
172+
{{- if .Values.monitoring.enabled }}
173+
annotations:
174+
{{- range $key, $value := .Values.monitoring.annotations }}
175+
{{ $key }}: {{ $value | quote }}
176+
{{- end }}
177+
{{- end }}
178+
```
179+
180+
## Use Template Variables for Reusability
181+
182+
Define template variables to avoid repeating complex expressions:
183+
184+
```yaml
185+
# ✅ GOOD: Define once, use many times
186+
{{- $appName := .Values.app.name | lower | kebabcase -}}
187+
{{- $namespace := .Values.app.namespace | default "default" -}}
188+
189+
apiVersion: v1
190+
kind: Service
191+
metadata:
192+
name: {{ $appName }}-service
193+
namespace: {{ $namespace }}
194+
spec:
195+
selector:
196+
app: {{ $appName }}
197+
---
198+
apiVersion: apps/v1
199+
kind: Deployment
200+
metadata:
201+
name: {{ $appName }}-deployment
202+
namespace: {{ $namespace }}
203+
spec:
204+
selector:
205+
matchLabels:
206+
app: {{ $appName }}
207+
208+
# ❌ AVOID: Repeating the same expression
209+
apiVersion: v1
210+
kind: Service
211+
metadata:
212+
name: {{ .Values.app.name | lower | kebabcase }}-service
213+
namespace: {{ .Values.app.namespace | default "default" }}
214+
spec:
215+
selector:
216+
app: {{ .Values.app.name | lower | kebabcase }}
217+
```
218+
219+
## Handle Type Conversions Explicitly
220+
221+
Convert types explicitly when needed:
222+
223+
```yaml
224+
# ✅ GOOD: Explicit type conversion
225+
apiVersion: v1
226+
kind: ConfigMap
227+
data:
228+
# Numbers must be strings in ConfigMaps
229+
port: {{ .Values.app.port | toString | quote }}
230+
replicas: {{ .Values.app.replicas | toString | quote }}
231+
232+
# Booleans to strings
233+
debug: {{ .Values.app.debug | toString | quote }}
234+
235+
# ❌ RISKY: Type mismatch errors
236+
data:
237+
port: {{ .Values.app.port }} # May fail if port is a number
238+
```
239+
240+
**Common conversions:**
241+
- `toString` - Convert to string
242+
- `toInt` - Convert to integer
243+
- `toYaml` - Convert object to YAML
244+
- `toJson` - Convert object to JSON
245+
246+
## Test Templates Before Deployment
247+
248+
Always preview your templates before deploying:
249+
250+
```bash
251+
# Preview manifests with default values
252+
zarf dev inspect manifests --features="values=true"
253+
254+
# Preview with custom values
255+
zarf dev inspect manifests \
256+
--features="values=true" \
257+
-f custom-values.yaml
258+
259+
# Preview with inline overrides
260+
zarf dev inspect manifests \
261+
--features="values=true" \
262+
--set-values="app.replicas=5,app.environment=staging"
263+
```
264+
265+
**What to check:**
266+
- Templates render without errors
267+
- Output looks as expected
268+
- No unexpected whitespace
269+
- Values are correctly interpolated
270+
- Conditionals evaluate correctly
271+
272+
## Handle Missing Values Gracefully
273+
274+
Protect against missing or nil values:
275+
276+
```yaml
277+
# ✅ GOOD: Safe against missing values
278+
{{- if .Values.monitoring }}
279+
{{- if .Values.monitoring.enabled }}
280+
annotations:
281+
prometheus.io/scrape: "true"
282+
{{- end }}
283+
{{- end }}
284+
285+
# Alternative: Use default for the entire object
286+
{{- $monitoring := .Values.monitoring | default dict -}}
287+
{{- if $monitoring.enabled }}
288+
annotations:
289+
prometheus.io/scrape: "true"
290+
{{- end }}
291+
292+
# ❌ RISKY: Will fail if monitoring is undefined
293+
{{- if .Values.monitoring.enabled }}
294+
annotations:
295+
prometheus.io/scrape: "true"
296+
{{- end }}
297+
```
298+
299+
## Be Consistent with Indentation
300+
301+
Use the `indent` and `nindent` functions consistently:
302+
303+
```yaml
304+
# ✅ GOOD: Proper indentation
305+
apiVersion: v1
306+
kind: ConfigMap
307+
data:
308+
config.yaml: |
309+
{{ .Values.app.config | toYaml | indent 4 }}
310+
311+
# ✅ ALSO GOOD: Using nindent (newline + indent)
312+
apiVersion: v1
313+
kind: ConfigMap
314+
data:
315+
config.yaml: |-
316+
{{- .Values.app.config | toYaml | nindent 4 }}
317+
318+
# ❌ AVOID: Inconsistent indentation
319+
apiVersion: v1
320+
kind: ConfigMap
321+
data:
322+
config.yaml: |
323+
{{ .Values.app.config | toYaml }}
324+
```
325+
326+
**Choose the right function:**
327+
- `indent N` - Indent every line by N spaces
328+
- `nindent N` - Add newline, then indent by N spaces
329+
330+
## Common Errors and Solutions
331+
332+
### Template Parse Errors
333+
334+
**Error:**
335+
```
336+
Error: template: :5:14: executing "" at <.Values.app.missing>: map has no entry for key "missing"
337+
```
338+
339+
**Solution:**
340+
```yaml
341+
# Use default for optional values
342+
value: {{ .Values.app.missing | default "fallback" }}
343+
344+
# Or check if exists first
345+
{{- if .Values.app.missing }}
346+
value: {{ .Values.app.missing }}
347+
{{- end }}
348+
```
349+
350+
### Type Errors
351+
352+
**Error:**
353+
```
354+
Error: wrong type for value; expected string; got int
355+
```
356+
357+
**Solution:**
358+
```yaml
359+
# Convert types explicitly
360+
value: {{ .Values.app.port | toString | quote }}
361+
```
362+
363+
### Whitespace Issues
364+
365+
**Problem:** Extra blank lines or spacing in output
366+
367+
**Solution:**
368+
```yaml
369+
# Use whitespace control operators
370+
{{- if .Values.app.debug -}}
371+
debug: true
372+
{{- end -}}
373+
```
374+
375+
### Quoting Problems
376+
377+
**Problem:** YAML parsing fails with boolean-like strings
378+
379+
**Solution:**
380+
```yaml
381+
# Always quote string values
382+
value: {{ .Values.app.setting | quote }}
383+
```
384+
385+
## Related Documentation
386+
387+
- [Templating Reference](/ref/templating/) - Complete templating documentation
388+
- [Package Values](/ref/package-values/) - Defining and using package values
389+
- [Sprig Function Documentation](http://masterminds.github.io/sprig/) - Complete Sprig reference
390+
- [values-templating Example](https://github.com/defenseunicorns/zarf/tree/main/examples/values-templating) - Working examples

0 commit comments

Comments
 (0)