Capabilities
The capabilities namespace exposes what the template knows about the cluster
it renders against. The two useful pieces are the Kubernetes version (for gating
version-specific fields) and the lookup function (for reading live resources).
capabilities.kubeVersion
Hull stores the cluster version under capabilities.kubeVersion. The keys keep
their Go-struct casing (capitalised), unlike the lowercase root namespaces.
| Path | Meaning |
|---|---|
capabilities.kubeVersion.Version |
full version string, e.g. 1.30.2 |
capabilities.kubeVersion.GitVersion |
server’s reported git version |
capabilities.kubeVersion.Major |
major, e.g. 1 — live cluster only |
capabilities.kubeVersion.Minor |
minor, e.g. 30 — live cluster only |
Against a live cluster (hull install / upgrade / diff), all four are
populated from the API server. Under hull template, only Version and
GitVersion are set — both to the --kube-version value — and Major / Minor
are always null.
There is no default version. If you run hull template without
--kube-version, capabilities.kubeVersion is unset and any version gate
errors. Pass the flag when a template branches on the version:
Template:
apiVersion: v1
kind: ConfigMap
metadata:
name: caps
data:
version: "${capabilities.kubeVersion.Version}"
major: "${capabilities.kubeVersion.Major}"
hull template ./pkg --kube-version 1.30.2:
apiVersion: v1
kind: ConfigMap
metadata:
name: caps
data:
version: "1.30.2"
major: null
Branching on the version
Use semverCompare with the version on the left of the pipe and the
constraint as a literal argument. The pipeline form is required — a function
argument is a literal, not a path (see
Expressions), so only the
left side is resolved:
$if: ${capabilities.kubeVersion.Version | semverCompare '>=1.27.0-0'}
spec:
template:
spec:
schedulingGates:
- name: hold
With --kube-version 1.30.2, ${... | semverCompare '>=1.27.0-0'} renders
true.
Refusing an unsupported cluster
$if: ${capabilities.kubeVersion.Version | semverCompare '<1.25.0-0'}
data: ${'this package requires Kubernetes 1.25 or newer' | fail}
fail aborts the render with your message.
capabilities.apiVersions
The set of group/versions the cluster has registered. Hull fills it from API
discovery on live operations, and from --api-versions (repeatable) when
running offline. The value is a map keyed by group/version.
There is no method call, slash-path, or bracket access in hull’s native
${...} engine, so none of these work:
${capabilities.apiVersions.has('apps/v1')} # no method call
${capabilities.apiVersions.apps/v1} # slash splits the path
${capabilities.apiVersions['apps/v1']} # no bracket form
(The .Has method exists only in Helm-compatibility mode, which uses Go
{{ }} templates, not hull expressions.) So there is no clean template-time
“is this GVK registered?” check. Practical alternatives:
- Trust the operator — assume the GVK is present when the package’s
kubeVersionconstraint covers it, or when CI passes--api-versions. - Render unconditionally and let the API server reject a missing kind;
with
--cleanup-on-failthe failure rolls back cleanly. - Gate at install time — a
pre-installhook runskubectl get crd ... || exit 1before the install proceeds.
For version-driven branching, kubeVersion + semverCompare is usually enough.
lookup — reading cluster state
lookup pulls a live resource into the render:
lookup(apiVersion, kind, namespace, name)
All four arguments are literals. It returns the resource as a map, or a falsy
value when the resource is absent — nil under hull template (no cluster),
an empty map against a live cluster. Pass an empty name to list a kind.
Guard on the result so a missing resource simply drops the block:
Template:
$if: ${lookup 'v1' 'ConfigMap' 'kube-system' 'cluster-info'}
data:
found: "yes"
Under hull template this renders nothing — lookup returns nil, $if is
falsy.
To read a field out of the result, pipe it through get or dig rather than a
dotted sub-path (the engine can’t index the result of a call inline):
data:
ca: ${lookup 'v1' 'ConfigMap' 'kube-system' 'cluster-info' | get 'data' | get 'ca.crt'}
dig walks several keys and takes a fallback as its last argument:
data:
ca: ${lookup 'v1' 'ConfigMap' 'kube-system' 'cluster-info' | dig 'data' 'ca.crt' ''}
Hull caches lookup results within a single render, so repeated lookups of the
same tuple hit the API server once.
Offline behaviour
Under hull template (no kubeconfig):
capabilities.kubeVersion.Version/.GitVersioncome from--kube-version; omit the flag and they are unset (nil).capabilities.kubeVersion.Major/.Minorare always null.capabilities.apiVersionscontains exactly the--api-versionsentries.lookupreturns nil for everything.
Templates that depend on lookup therefore take their empty/false branches
under hull template. To preview against a real cluster’s view, use hull diff
or hull plan, which read live capabilities and lookups.
See also
- Expressions — why
semverCompareneeds the pipeline form - Function reference —
semverCompare,fail,get,dig - Control flow —
$ifgating on capabilities andlookup