Course outline

Helm / Basic: chart and rendering foundations / Lesson 5

Control flow, scope and whitespace

Use conditionals and loops with explicit context, keeping generated documents readable and preserving valid YAML boundaries.

About 4 min readContent reviewed
On this page

Lesson goals

  • Explain how with and range change the meaning of dot.
  • Retain root context and local variables deliberately.
  • Review whitespace trimming through its rendered effect.

Start with the current context

The dot represents the current template context. At a template entry point it normally exposes objects such as Values and Release. Inside with or range, dot changes to the selected value or current element. That change makes short expressions convenient, but a nested .Release.Name can fail because Release is not a field of the selected map. Do not repair such an error by adding fake Release data to values. Instead decide which context each expression needs. A short scope diagram often explains the failure faster than repeatedly editing indentation or quoting.

Retain the root explicitly

A variable assigned before a block can preserve a needed value. The root variable $ also gives access to the context supplied at the template entry point, although a helper can receive a deliberately different root. The first fragment stores Release.Name and uses it inside with. Assume settings.mode is demo and Release.Name is preview-catalog; both intended data entries are strings. The variable does not fetch new information or escape an authorization boundary. It is simply a reference to already supplied context, which makes data dependencies visible to reviewers.

gotemplate Preserve one root value
{{- $releaseName := .Release.Name -}}
data:
  {{- with .Values.settings }}
  mode: {{ .mode | quote }}
  release: {{ $releaseName | quote }}
  {{- end }}
Expected output
data:
  mode: "demo"
  release: "preview-catalog"

Within with, dot is settings. The variable remains available from its enclosing scope. This is an illustrative manifest fragment.

Use explicit loop variables

A range can bind the index and value for a list or the key and value for a map. Named variables reduce confusion between an element and the entire values tree. The second fragment expects a list of two strings, public and internal, and emits indexed string entries in a ConfigMap data subtree. It is a teaching example, not a recommendation to store arbitrary application lists this way. When order matters to the application, expose a list rather than relying on accidental map presentation. Consider the empty case: a zero-iteration loop can leave a null subtree rather than an empty map.

gotemplate List index and element
data:
  {{- range $index, $name := .Values.channels }}
  channel-{{ $index }}: {{ $name | quote }}
  {{- end }}
Expected output
data:
  channel-0: "public"
  channel-1: "internal"

For channels [public, internal], the intended keys are channel-0 and channel-1. Each value is quoted as required by ConfigMap.data.

Trim whitespace cautiously

The hyphen in a template delimiter trims adjacent whitespace, including newlines. Trimming both sides of a conditional can accidentally join two YAML keys onto one line. Because the template language does not know YAML structure, a syntactically valid template can still create invalid output. Decide where a newline must survive, then place trimming markers accordingly. Review present and absent branches, not just the path used during development. A missing optional block often changes neighboring indentation or document separators, so its output deserves the same scrutiny as the populated case.

Design small branch matrices

A useful review matrix covers enabled and disabled resources, absent and present maps, empty and populated lists and intentional false values. These cases exercise different control-flow paths without needing a large cluster. Keep each values fixture tiny and explain the expected resource count or subtree. Do not expand every possible combination blindly; choose cases that represent distinct structural outcomes. When a scope issue appears, isolate the smallest block and identify the value of dot at each line before introducing helpers. This approach turns a vague rendering failure into a precise input-context mismatch.

Common errors and fixes

Release.Name cannot be evaluated inside with

Why it happens
Dot now points to the selected settings map.
How to fix it
Use a preserved variable or the appropriate root context.
Prevent it next time
Annotate scope boundaries during template review.

Two YAML keys appear on one line

Why it happens
Whitespace trimming removed a required newline.
How to fix it
Retain the line boundary and review both conditional branches.
Prevent it next time
Check rendered text rather than template indentation alone.

An empty loop produces an unintended null map

Why it happens
The parent key remained while no children were emitted.
How to fix it
Omit the entire optional subtree or emit an explicit empty structure when its schema allows it.
Prevent it next time
Include zero-item fixtures in the review matrix.

Check your understanding

1. Inside range .Values.channels, which expression accesses the original release name?

Show worked solution

At the top-level template, $.Release.Name accesses the original root, or a variable captured before range can hold the name. Inside a helper invoked with another context, $ refers to that supplied context instead, so the helper contract must be known. Do not assume root always means the original chart root across all helper calls.

2. An optional annotations block works when populated but fails when absent. What should you inspect first?

Show worked solution

Compare the generated text around the parent key and adjacent lines. Look for a retained null key, indentation shifted by the conditional or newlines consumed by trimming. Confirm the input is genuinely absent rather than a wrong type. Fix the structural branch and retain both fixtures so the successful populated path cannot hide another regression.

Key takeaways

  • Dot changes with scope; root and captured variables have explicit calling contracts.
  • Empty branches and whitespace are part of the generated document contract.

Official references