skip to content

A Helm named template needs two inputs, but `include` takes one argument — how do you pass both?

level: middleimportance: should knowfreq 48%

answer

  1. A partial has exactly one parameter
  2. Bundle the inputs into a single value
  3. Alternating keys and values build a map
  4. Carry the root context in under a key

basics

~20 s

Bundle the inputs into one value with the dict function and pass that: include "fraud-scoring.probe" (dict "ctx" $ "port" .Values.service.port). Inside the partial, dot is the dict, so the inputs are read as .ctx and .port.

solid answer

~50 s

A named template receives exactly one value, bound to dot, so multiple inputs have to travel inside a single structure. The idiom is `dict`, which builds a map from alternating key/value pairs: `{{ include "fraud-scoring.probe" (dict "ctx" $ "port" .Values.service.port "delay" 7) }}`. Inside the partial you read `.port` and `.delay`. Because dot is now the dict and not the render context, `.Values`, `.Release` and `.Chart` are unreachable — which is why the convention is to pass the root context in under a key such as `ctx` or `top` and write `.ctx.Release.Name` inside. Note that `$` inside the partial is also the dict, so it is no escape hatch. Defaults are handled either by merging the caller's dict over a defaults dict, or more simply with the `default` function on each key inside the partial.

code

yaml · 11 lines
yaml
{{- define "fraud-scoring.probe" -}}
httpGet:
  path: /healthz
  port: {{ .port }}
initialDelaySeconds: {{ .delay | default 5 }}
{{- end }}
containers:
  - name: scorer
    image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
    readinessProbe:
      {{- include "fraud-scoring.probe" (dict "ctx" $ "port" .Values.service.port "delay" 7) | nindent 6 }}

go deeper

for a junior

Remember the shape of the call: one argument only, so wrap several inputs in dict "key" value "key" value and put the whole thing in parentheses. Inside the partial, read them by key.

for a middle

Explain why dot inside the partial is now the dict and what that costs you — .Values and .Release are gone unless you pass the context in under a key, and $ does not get them back.

for a senior

Talk about the partial's argument map as an interface: document the keys, validate the required ones, keep the set small, and never mutate values-derived maps to smuggle data between templates.

for a principal

Own the chart-wide convention — one key name for the context, a documented parameter map per partial, and a rule for when a helper should take plain dot instead. Consistency here is what keeps a shared chart library reviewable.

## The constraint Calling a named template — with `include` or with the `template` action — passes exactly one value, and that value becomes dot inside the partial. There is no parameter list, no keyword arguments, and no way to add a second positional argument. Any partial that needs more than one input therefore needs its caller to bundle those inputs into a single value. ## The dict idiom The standard bundle is a map built by the `dict` function, which takes alternating keys and values: ``` {{- include "fraud-scoring.probe" (dict "ctx" $ "port" .Values.service.port "delay" 7) }} ``` The parentheses matter: they make the whole `dict` call one expression, evaluated first, so `include` sees a single argument. Inside the partial, dot is that map, and each input is read by key — `.port`, `.delay`. A missing key is simply absent, so `{{ .delay | default 5 }}` inside the partial is the cheap way to make a parameter optional. ## Why the root context usually rides along Rebinding dot to the dict has a consequence that surprises people: the render context is gone. `.Values`, `.Release`, `.Chart` and `.Capabilities` are all fields of the context that the manifest was rendering with, and the partial no longer holds that context — it holds your two-key map. `$` is no rescue either: it is bound to the data value the current template execution started with, which for an included partial is the argument you passed, so `$` and dot are the same dict. That is why almost every real chart passes the context in as one of the keys, conventionally named `ctx`, `top` or `root`: ``` {{- include "fraud-scoring.probe" (dict "ctx" $ "port" 8443) }} ``` and inside the partial writes `{{ .ctx.Release.Name }}` or `{{ .ctx.Values.image.tag }}` alongside `{{ .port }}`. Pick one key name and use it across the whole chart; a chart where half the partials say `ctx` and half say `top` produces exactly the sort of empty render that costs an afternoon. ## Building the dict from existing data `dict` is not the only way to make the bundle. A partial that mostly wants an existing map with one extra field can be handed a merged map — merge the caller's small dict over a defaults dict — and a partial that genuinely wants a list can be given one with `list`. Two habits are worth avoiding. First, do not build a positional list and index into it inside the partial (`index . 0`, `index . 1`): the call site stops being readable and reordering breaks silently. Second, do not mutate the context: `set` on a map derived from `.Values` changes shared data for every later template in the render, which produces order-dependent output that is miserable to debug. Build a fresh dict instead. ## Variables do not cross the boundary Another instinct is to set a variable in the caller and read it in the partial. Template variables are lexically scoped to the template that declares them, so `$port` defined in the Deployment is invisible inside the partial; the partial sees only its argument. The same applies in the other direction: a variable assigned inside the partial does not leak back. The single argument really is the entire interface, which is a feature — it makes a partial testable by rendering it with a known dict. ## Designing the partial's interface Because the interface is one map, it pays to treat it like a function signature. Document the keys the partial expects in a comment above the `define`. Validate the ones that must be present, so a caller that forgets a key gets a clear failure rather than a Deployment with an empty port. Keep the key set small: a partial taking eight keys is usually two partials, or a partial that should have been given the context and read the values itself. A useful rule of thumb: if the partial is generic — a probe block, a resource block, a name truncator — take explicit keys plus `ctx`. If the partial is really "the labels for this chart", take plain `.` and read `.Values` and `.Release` directly; wrapping that in a dict adds ceremony and nothing else. ## Reading a failure When a dict-taking partial renders an empty or half-built block, the diagnosis is almost always one of three things: the caller passed `.` where the partial expects a dict, so every key lookup is missing; the caller passed a dict where the partial expects the context, so `.Values` is missing; or a key name is misspelled on one side. Rendering the chart and reading the block that came out — rather than the template — tells you which, because a missing key renders as nothing at all rather than as an error.

  • Inside a partial called with a dict, why can't you just write `$.Release.Name`?
    Because `$` is bound to the data the current template execution was given, and for an included partial that is the argument you passed — the dict. So `$` and dot are the same map there, and `$.Release` is missing rather than the release object. The root context has to be handed in explicitly, which is why the convention is a `ctx` or `top` key holding `$` from the call site.
  • How do you give a dict-taking partial sensible defaults for keys the caller omits?
    Two ways. Simplest is to apply `default` at each use inside the partial — `{{ .delay | default 5 }}` — which keeps the defaults next to the thing they describe. For a larger parameter set, merge the caller's dict over a defaults dict at the top of the partial and use the merged result throughout. Either way, validate keys that have no sensible default so a forgotten one fails loudly instead of rendering blank.

saying these in an interview costs you the question

  • Passes two arguments to include and expects both to arrive
  • Uses $ inside the partial to reach the root context
  • Thinks a variable set in the caller is visible in the partial
  • Assumes every partial receives the full render context
  • Passes a positional list and indexes into it by number

context