控制结构与变量

Control Structures 控制结构

参考:官方文档

控制结构在模板板中被称为 actions(动作)

Helm 模板中有如下几种控制结构:

  • if/else    # 一个条件判断的代码块
  • with     # 用于更改当前作用域
  • range    # 用于循环遍历数组或者 map。

除此之外,还提供了一些声明和使用命名模板的控制结构:

  • define    # 在模板中声明一个新的命名模板
  • template    # 导入一个命名模板
  • block    # 声明了一种特殊的可填写模板区域

define、template、block 这三种控制结构,放在 named template(命名模板) 中进行详解,详见:Named Templates(命名模板)

在下文的各种示例中,我们使用下面这种数据,来让各种控制结构进行处理,values.yaml 文件如下:

favorite:
  drink: water
  food: sushi
  game: 'WOW & PAL'

sushiKinds:
- sashimi
- name: hot
- name: handRoll
  rice: more
- name:

IF/ELSE

if/else 判断语句的语法如下:

{{ if PIPELINE }}
  # Do something
{{ else if OTHER PIPELINE }}
  # Do something else
{{ else }}
  # Default case
{{ end }}

当 PIPELINE 值为以下内容,判定为 false:

  • 布尔值 false
  • 数字零
  • 一个空字符串
  • nil
  • 一个空的集合(map,slice,tuple,dict,array)

下列模板将判断如果 .Values.favorite.drink == “water” 则新增 mug: true 。

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  drink: {{ .Values.favorite.drink | default "tea" | quote }}
  food: {{ .Values.favorite.food | upper | quote }}
  {{ if eq .Values.favorite.drink "water" }}mug: true{{ end }}

With

{{ with PIPELINE }}
  # with声明的作用域
{{ end }}
复制代码

with 用于更改当前作用域(.)。上文提到在{{ .Release.Name }}中,最左边的(.)表示当前作用域下的顶层命名空间,.Values 告诉模板在当前作用域范围的顶层命名空间下查找 Values 对象。使用 with 可以改变模板变量的当前作用域,把(.)赋值给另一个对象:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  {{- with .Values.favorite }}
  drink: {{ .drink | default "tea" | quote }}
  food: {{ .food | upper | quote }}
  {{- end }}

在上面的例子中,在 with 的作用范围内({- with .xxxx} 到 {{- end}}之间)可以直接引用.drink 和.food,这是因为{{- with .Values.favorite}}把 Values.favorite 赋值给了当前作用域(.)。

range

用于循环遍历 array 或 map 。range 与 go 语言中的 for…range 关键字的行为相同,只是语法不同。语法如下:

{{ range [$STRING1, $STRING2 :=] PIPELINE }}
# do something
{{end}}
  • PIPELINE 用于产生 map 或者 array 类型的数据;非这两类的数据,range 无法处理。
  • $STRING1 与 $STRING2 是可选的。通过 range 处理的 PIPELINE 可以返回两个参数。

range 在处理 array 与 map 这两类数据时,方式是不同的。在 data 字段下,展示 range 处理 map 与 array 这两类数据时的四种不同情况:

  • map # 使用 range 循环处理 map。map 范围下的 {{ . }} 为每个 map 中键值对的值
  • mapVariable    # 两个变量分别为 map 数据的 key 和 value。也就是说 {{ . }} 的值与 $element 的值相同。
  • array # 使用 range 循环处理 array。array 范围下的 {{ . }} 为数组中每个元素的值。
  • arrayVariables # 两个变量分别为 array 数据的 索引 和 元素。也就是说 {{ . }} 的值与 $value 的值相同。
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-range-configmap
data:
  map:
    {{.Values.favorite}}
    {{- range .Values.favorite}}
    {{ . }}
    {{- end }}
  mapVariable:
    {{- range $key, $val := .Values.favorite}}
    {{ $key }}: {{ $val }}
    {{- end }}
  array:
    {{- /* 如果要添加下面这行,则需要在 array:后面添加 | 符号,否则会报错:helm did not find expected ',' or ']' */}}
    {{/*.Values.sushiKinds*/}}
    {{- range .Values.sushiKinds}}
    {{ . }}
    {{- end }}
  arrayVariables:
    {{- range $index, $element := .Values.sushiKinds}}
    索引 {{ $index }} 号元素的值为:{{ $element }}
    {{- end }}

渲染结果如下:

[root@master-1 control_structures]# helm template ctrl . -s templates/range.yaml
---
# Source: control_tructures/templates/range.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ctrl-range-configmap
data:
  map:
    map[drink:water food:sushi game:WOW & PAL]
    water
    sushi
    WOW & PAL
  mapVariable:
    drink: water
    food: sushi
    game: WOW & PAL
  array: |-
    [sashimi map[name:hot] map[name:handRoll rice:more] map[name:<nil>]]
    sashimi
    map[name:hot]
    map[name:handRoll rice:more]
    map[name:<nil>]
  arrayVariables:
    索引 0 号元素的值为:sashimi
    索引 1 号元素的值为:map[name:hot]
    索引 2 号元素的值为:map[name:handRoll rice:more]
    索引 3 号元素的值为:map[name:<nil>]

注意:如果想要继续获取 map[] 中的每个 key/value 对的值,则需要再次使用 range 来获取 map[] 中的值。就是在 sushiKinds1-2 中使用 sushiKinds1-1 的指令。只不过有一点需要主要,在这个例子中,3 号元素值并不是 map ,也就无法使用 range。所以这种不同类型的数据结构,最好不要混用,否则模板很难处理。

也就是说 Helm 里的 range 控制结构如法处理 map 与 array 的混合数据数据类型,只能同类处理。

map 与 array 的混合数据(比如这种 - sashimi: good 结构的数据),{{- range $key, $val := .Values.favorite}} 这种指令渲染效果不好。渲染结果是这样的: 0: map[sashimi:good],并不能将 map 中的键值对直接拆开

通过 range 来梳理.范围内的对象

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-test-configmap
datadot: {{ . }}
datadotrange: |-
{{- range $index, $value := .Release}}
  {{$index}}: {{ $value }}
{{- end}}

从这里可以看到输出结果:

[root@master-1 test]# helm template test .
---
# Source: test/templates/test.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: test-test-configmap
# 实际上, . 范围内的数据就是一个具有6个 map 的数组。是 map 与 array 的混合结构数据
datadot: map[Capabilities:0x2a66d80 Chart:0xc00015aa20 Files:map[] Release:map[IsInstall:true IsUpgrade:false Name:test Namespace:default Revision:1 Service:Helm] Template:map[BasePath:test/templates Name:test/templates/test.yaml] Values:map[]]
# 而 .Release 范围内的数据,也具有多个元素。
datadotrange: |-
  IsInstall: true
  IsUpgrade: false
  Name: test
  Namespace: default
  Revision: 1
  Service: Helm

从输出结果来看, . 范围下的对象有 Capabilities、Chart、Files、Release、Template、Values 这几个。正好与 Helm Template 介绍 中描述的 内置对象 匹配上。

toYaml 函数

toYaml 用来遍历 PIPELINE 范围内的每一行,toYAML 的效果相当于 range 的简化版,把 yaml 文件中 PIPELINE 范围内的所有内容原封不动导入到模板中。常用来处理注释类数据。

语法:

{{toYAML PIPELINE}}

示例:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-test-configmap
data:
  {{- toYaml .Values | nindent 2}}

这时,会将 annotations 下的一组键值对都传进模板,如果不用 toYaml,是无法实现的,因为传递的值中有冒号。渲染结果如下:

[root@master-1 test]# helm template test .
---
# Source: test/templates/test.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: test-test-configmap
data:
  favorite:
    drink: water
    food: sushi
    game: WOW & PAL
  sushiKinds:
  - sashimi
  - name: hot
  - name: handRoll
    rice: more
  - name: null

range 中的 tuple 函数

有时能够快速在模板中创建一个列表,然后遍历该列表很有用。 Helm 模板具有简化此功能的功能:元组。在计算机科学中,元组是固定大小的列表式集合,但是具有任意数据类型。这大致传达了元组的使用方式。

  sizes: |-
    {{- range tuple "small" "medium" "large" }}
    - {{ . }}
    {{- end }}

上面会产生这个:

  sizes: |-
    - small
    - medium
    - large

Whitespace(空格)处理

官方文档

在上述的模板示例中,我们会在一些模板指令里面出现中划线-这个符号,例如{{- xxx}}或者{{xxx -}}。这个中划线的作用就是消除多于空格。例如有如下的模板:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  drink: {{ .Values.favorite.drink | default "tea" | quote }}
  food: {{ .Values.favorite.food | upper | quote }}
  {{ if eq .Values.favorite.drink "coffee" }}
  mug: true
  {{ end }}

其渲染结果是:

apiVersion: v1
kind: ConfigMap
metadata:
  name: telling-chimp-configmap
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "PIZZA"

  mug: true

可以看到,在 mug: true 前后各多了一个空行,这是因为在模板渲染的过程中,删除了{{}}中的内容,但保留了其余的空白。使用-符号来消除模板语句占用的空格,为直观展示删除效果,在下列示例中,星号(*)表示被删除的空格:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
data:
  myvalue: "Hello World"
  drink: {{ .Values.favorite.drink | default "tea" | quote }}
  food: {{ .Values.favorite.food | upper | quote }}*
**{{- if eq .Values.favorite.drink "coffee" }}
  mug: true*
**{{- end }}

渲染结果:

...
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "PIZZA"
  mug: true
...

中横线在左边{{- xxx}}表示消除左边的空格,中横线在右边{{xxx -}}表示消除右边的空格。要小心不要写成了:

...
  food: {{ .Values.favorite.food | upper | quote }}
  {{- if eq .Values.favorite.drink "coffee" -}}
  mug: true
  {{- end -}}
...

这样渲染出来的结果会是food: "PIZZA"mug:true。 除了使用-消除模板的空格外,Helm 还提供了 indent 函数增加空格来进行缩进:

...
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "PIZZA"
{{ "mug: true" | indent2 }}  # 为"mug: true"增加两个空格的缩进。
...

Variables 变量

变量在大部分变成语言都是基本组成部分,但是在 Helm Template 中使用率较低。一般常用来优化 with 与 range 控制结构。

比如下面的示例,引用对象的范围不在在 with 中指定的范围内时,这时候可以通过变量来解决。

  {{- with .Values.favorite}}
  drink: {{.drink | default "tea" | quote}}
  food: {{.food | upper | quote}}
  release: {{.Release.Name}}
  {{- end}}

这个示例中 release: {{.Release.Name}} 引用将会失败,因为该引用在 with 规定的 .Valuse.favorite 这个范围内,在这个范围内,没有 Release 对象。

这时候,变量就派上用场了。

变量的定义与引用格式:

# 定义一个名为 NAME 的变量。通过 := 符号对变量进行赋值
$NAME := PIPELINE
# 引用变量 NAME
{{ $NAME }}

现在修改以下文章开头的模板

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{.Release.Name}}-configmap
data:
  myvalue: "Hello World"
  {{- $relname := .Release.Name -}}
  {{- with .Values.favorite}}
  drink: {{.drink | default "tea" | quote}}
  food: {{.food | upper | quote}}
  release: {{$relname}}
  {{- end}}

这样,在 with 控制结构中的 release 就有值了~

有一个始终是全局变量 $ ,此变量将始终指向根上下文。当您在一个范围内循环并且需要知道图表的发行名称时,这可能非常有用。

$ 符号可以起到转义符的作用,试得在一个范围内的 . 符号可以在全局中引用其内的对象。比如

{{- range .Values.tlsSecrets }}
apiVersion: v1
kind: Secret
metadata:
  name: {{ .name }}
  labels:
    # Many helm templates would use `.` below, but that will not work,however `$` will work here
    app.kubernetes.io/name: {{ template "fullname" $ }}
    # 无法引用.Chart.Name,但可以执行 $.Chart.Name 来引用.Chart.Name
    helm.sh/chart: "{{ $.Chart.Name }}-{{ $.Chart.Version }}"
    app.kubernetes.io/instance: "{{ $.Release.Name }}"
    app.kubernetes.io/version: "{{ $.Chart.AppVersion }}"
    app.kubernetes.io/managed-by: "{{ $.Release.Service }}"
type: kubernetes.io/tls
data:
  tls.crt: {{ .certificate }}
  tls.key: {{ .key }}
---
{{- end }}

最后修改 August 6, 2023: update (29302470)