helm: nil pointer evaluating interface when upper level doesn't exist prevents usage of default function
Hello, I’ll take an example template but the issue is more global than that I think.
I define a simple Service template:
apiVersion: v1
kind: Service # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#service-v1-core
metadata:
name: MyService
spec:
type: {{ .Values.service.type | default "ClusterIP" }}
ports:
- port: 8080
targetPort: 8080
protocol: TCP
name: alt-http
In my values.yaml, when I define
service:
type: somevalue
Everything goes well. It does also when I define the following:
service: {}
But if I don’t define service at all, I get an error:
╰ helm template . --debug
install.go:159: [debug] Original chart version: ""
install.go:176: [debug] CHART PATH: /home/mtodorovic/git/helm-issue/helm-chart
Error: template: helm-chart/templates/service.yaml:6:18: executing "helm-chart/templates/service.yaml" at <.Values.service.type>: nil pointer evaluating interface {}.type
helm.go:84: [debug] template: helm-chart/templates/service.yaml:6:18: executing "helm-chart/templates/service.yaml" at <.Values.service.type>: nil pointer evaluating interface {}.type
As soon as the parent value doesn’t exist, templating fails and totally ignores the default function. I expected that if anything goes wrong in .Values.service.type evaluation, we’ll go to default "ClusterIP" but it doesn’t.
We workaround the issue with the following but it wouldn’t work as easily for big dicts (like dict.bigkey.subkey.key)
{{- $service := .Values.service | default dict -}} # workaround for https://github.com/helm/helm/issues/8026
apiVersion: v1
kind: Service # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#service-v1-core
metadata:
name: MyService
spec:
type: {{ $service.type | default "ClusterIP" }}
ports:
- port: 8080
targetPort: 8080
protocol: TCP
name: alt-http
I didn’t deep dive into the code yet, is it something that looks like fixable?
Thanks and keep the nice work with Helm 🎉
Output of helm version:
version.BuildInfo{Version:“v3.2.0”, GitCommit:“e11b7ce3b12db2941e90399e874513fbd24bcb71”, GitTreeState:“clean”, GoVersion:“go1.13.10”}
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 83
- Comments: 36 (5 by maintainers)
Commits related to this issue
- fix nil pointer because default Values are missing https://github.com/helm/helm/issues/8026 — committed to apnmt/k8s-config by tobi5775 2 years ago
- fix nil pointer because default Values are missing https://github.com/helm/helm/issues/8026 — committed to apnmt/k8s-config by tobi5775 2 years ago
- fix: remove slugifies, root problem was solved https://github.com/helm/helm/issues/8026#issuecomment-881216078 — committed to cubos/kubesdk by dgadelha 2 years ago
- Fix nil pointer error in secrets list I tried upgrading the portal-jsonnet Helm charts and ran into a "nil pointer" error. See: https://github.com/helm/helm/issues/8026#issuecomment-881216078 — committed to mintel/helm-charts by jtdoepke 2 years ago
this has worked for me:
The inability to go beyond 1 level deep in the usage of default is a big limitation to what you can do and kind of limits the intention of the “default” function.
From the documentation of default
There is no mention of depth here.
Fixing this would be a great benefit to chart devs and users.
Unfortunately, this is not something that can be fixed.
This occurs because the
.Valuestree is populated based on the input provided through values.yaml,--values,--set, etc. It is not populated based on how the templates consume the input. If a value is not set,.Valueshas no knowledge of this sub-tree, and therefore it is evaluated as anilobject when rendered throughtext/template.There are two ways to mitigate this particular issue:
.Values.serviceTyperather than.Values.service.type)..Valuesobject can be properly populated (as @michael-todorovic mentions in their workaround withservice: {}). This will inform the rendering engine to create an empty leaf node.Wouldn’t this be beautifully solved if the parse could handle optional chaining similar to Javascript in latest standard ECMAScript 2020? See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
It would basically look like this:
This could be used in other cases, such as:
Workaround which I’m using (using the example from the previous comment):
I experience this error, even when the value is defined, when trying to expand it in a named template.
That is quickly becoming ugly with multiple levels of nesting.
Safe nesting can be achieved through the use of parenthesis:
Good catch, @saiskee.
From the documentation:
Looks like
{{ dig "service" "type" "ClusterIP" .Values }}should solve OP’s original issue.Thank you for your insight. I also had a range. Using the $ instead of . in order to use the global scope solved the problem:
Thanks sxyandapp, that works for me too.
@marko-curlin https://stackoverflow.com/a/68807258/1853417 explains it.
The link mentioned above has info on syntax, and there’s a couple samples here, but its seems for some cases this is still not a solution, I believe I’m bumping into https://github.com/helm/helm/issues/9266.
I suppose for such cases I’ll have to use conditionals.
EDIT: or actually use
(.Values | merge (dict))instead of .Values and this will work around it. Still not the same issue as describe here, and hopefully the other one will be “fixed” (as in support for this without using merge) is added.Looks like sprig added the new
digfunction, so in helm v3.5.0, you can now usedig.My issue was I had a range in one of the templates that was causing the issue so I need to change
image: {{ template "registry.repo.tag" . }}to
image: {{ template "registry.repo.tag" $root }}Got the same issue, but dig is not working for me due to:
Does that works in all Helm 3.X versions? And is it documented somewhere?
I just tested and using
digworks fine 👍 I didThanks for the tip @saiskee
虽然有很多括号,但这是目前看来最好的办法了
I created a very hacky way to evaluate dictionaries without having to worry about NPE being thrown. Here is the template:
(The line starting with # will be your output in this case)
Let’s say you have a values yaml:
Then you can use this to get the value of
foo.bar{{ include "common.util.safewalk" (list . ".Values.foo.bar") -}}if you were to do
it would return empty rather than throwing an error.