Helm: Values not exist with loop over a range

Created on 10 Oct 2016  ·  14Comments  ·  Source: helm/helm

Follow this issue#1055

trying to do the same think with:

{{ range $k, $v := until (atoi (quote .Values.Replicas) | default 5) }}
apiVersion: v1
kind: Service
metadata:
  name: zookeeper-{{$v}}
  namespace: {{ .Values.Namespace }}
  labels:
    node: "zookeeper-{{$v}}"
  annotations:
    "helm.sh/created": {{ .Release.Time.Seconds | quote }}
    service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
spec:
  ports:
  - port: {{.Values.PeerPort}}
    name: peer
  - port: {{.Values.LeaderElectionPort}}
    name: leader-election
  - port: {{.Values.ClientPort}}
    name: client
  selector:
    component: "{{.Release.Name}}-{{.Values.Component}}"
    zookeepernode: "zookeeper-{{$v}}"
{{ end }}

But i get this error:

Error: render error in "zookeeper/templates/deployment.yaml": template: zookeeper/templates/deployment.yaml:6:22: executing "zookeeper/templates/deployment.yaml" at <.Values.global.names...>: can't evaluate field Values in type int

May be i forget something, any idea?

Most helpful comment

@worldsayshi For what it's worth, I just ran into this same issue, and it turns out there's a relevant bit of documentation from the variables section:

However, there is one variable that is always global - $ - this variable will always point to the root context. This can be very useful when you are looping in a range need to know the chart’s release name.

Turns out it's simpler to just do something like this if you need access to the root context from within a range block:

{{- range $key, $value := .Values.someHash }}
  {{ $key }}: {{ print $.Values.prefix ":" $value | quote }}
{{- end }}

All 14 comments

finally i find a trick, but may be it's something we can improve.

{{- $root := . -}}

{{ range $k, $v := until (atoi (quote .Values.Replicas) | default 5) }}
apiVersion: v1
kind: Service
metadata:
  name: zookeeper-{{$v}}
  namespace: {{ $root.Values.Namespace }}
  labels:
    node: "zookeeper-{{$v}}"
  annotations:
    "helm.sh/created": {{ $root.Release.Time.Seconds | quote }}
    service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
spec:
  ports:
  - port: {{$root.Values.PeerPort}}
    name: peer
  - port: {{$root.Values.LeaderElectionPort}}
    name: leader-election
  - port: {{$root.Values.ClientPort}}
    name: client
  selector:
    component: "{{$root.Release.Name}}-{{$root.Values.Component}}"
    zookeepernode: "zookeeper-{{$v}}"
{{ end }}

Oh, yes... that is one of the more esoteric things about Go templates. The . scope gets overridden in a control structure.

I tried a few ways of working around this, but the best is the solution in the example above. Marking this closed.

This bit me as well :(

@technosophos Maybe you could consider changing this behavior? i.e. deprecate iterating with range without explicitly assigning variables, and preserve the scope

This is something that Go performs as part of text/template, so there's nothing actionable in Helm directly. Scope is changed when iterating through a control structure like range. Your best bet to change this behavior would be to file a bug upstream with Go itself.

relevant docs from text/template:

{{range pipeline}} T1 {{end}}

The value of the pipeline must be an array, slice, map, or channel. If the value of the pipeline has length zero, nothing is output; otherwise, dot is set to the successive elements of the array, slice, or map and T1 is executed. If the value is a map and the keys are of basic type with a defined order ("comparable"), the elements will be visited in sorted key order.

For some reason the $root workaround seemed to work for me momentarily but now it's choking on it:
Error: UPGRADE FAILED: unable to recognize "": no matches for /, Kind=Deployment
I didn't work on the project for a while. I have no idea what changed in between. Removing the $root assignment takes away this error.

Ok, so it started working again when I removed one of the - resulting in {{- $root := . }}. I'm probably misunderstanding something.

Edit: So some relevant text from Go template documentation is:

However, to aid in formatting template source code, if an action's left delimiter (by default "{{") is followed immediately by a minus sign and ASCII space character ("{{- "), all trailing white space is trimmed from the immediately preceding text. Similarly, if the right delimiter ("}}") is preceded by a space and minus sign (" -}}"), all leading white space is trimmed from the immediately following text. In these trim markers, the ASCII space must be present; "{{-3}}" parses as an action containing the number -3.

Edit2: This keeps getting weirder. If I add a printf statement immediately after the root assignment it works again. Sigh, whatever works I guess.

# We assign the current context to a variable to access it in range iteration below
{{- $self := . -}}
{{ printf "%#v" $ }}

(I'm actually using self as the assignment parameter instead of root)
Also, it doesn't work without adding an initial comment to the document.

@worldsayshi For what it's worth, I just ran into this same issue, and it turns out there's a relevant bit of documentation from the variables section:

However, there is one variable that is always global - $ - this variable will always point to the root context. This can be very useful when you are looping in a range need to know the chart’s release name.

Turns out it's simpler to just do something like this if you need access to the root context from within a range block:

{{- range $key, $value := .Values.someHash }}
  {{ $key }}: {{ print $.Values.prefix ":" $value | quote }}
{{- end }}

If anyone is coming here as confused as I was on how to apply @DeviateFish 's elegant solution to template functions (like the ones you get for free on helm create), you just need to pass the proper context into the template function.

{{ range tuple "fizz" "buzz" }}
...
{{ template "foobar.fullname" $ }}  # Worky.
{{ template "foobar.fullname" . }}  # No worky.
...
{{ end }}

Nice template from @killix ... one thing that took me a couple tries to spot was that the example at the beginning of this thread should have a --- at the end of the template to separate the services created in the loop. Hope that saves someone else a few minutes of debugging.

I have some issue with this scope and I get the error - Error: UPGRADE FAILED: template: maas360/charts/consumer/templates/_consumer.tpl:84:33: executing "loadconsumerConfigFiles" at <$root.Files.Glob>: nil pointer evaluating interface {}.Glob.

I have my configmap.yaml which calls one template as below -

{{- $root := . -}}
{{ range $instance_id, $value := .Values.instance_names }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ $.Release.Name }}-{{ $.Values.maasappname }}-{{$instance_id}}-configmap
data:

{{template "loadconsumerConfigFiles" (dict $instance_id) $}}

{{end}}

And I have this loadconsumerConfigFiles in template file -
{{- define "loadconsumerConfigFiles" -}}
{{- $root := . -}}
{{- $arg1 := index . "0" -}}
{{- $file := .Files }}
{{- range $path, $byte := $root.Files.Glob "config/*" }}

I get this error. Basically am trying to pass instance_id as a parameter to the loadconsumerConfigFiles so that I can use this inside that template. Could you please help what am i missing. I already have that root context passed. still it is showing the error.

This worked for me to reset the scope back to root and preserve the item I'm iterating over. This had the benefit of working around a bug in IntelliJ where it could not find the definition of variables referencing $root

{{- range .Values.deployments }}
{{- $rangeItem := . -}}
{{- with $ }}
---
apiVersion: apps/v1
kind: Deployment
  name: {{ $rangeItem.name }}
  labels:
    - organization: {{ .Values.organization }}
# etc etc
... 
{{- end }}
{{- end }}

ok, thanks a lot.

Was this page helpful?
0 / 5 - 0 ratings