流程控制
控制结构(在模板术语中称为“操作”)为模板作者提供了控制模板生成流程的能力。舵图模板语言提供了以下控制结构
if
/else
用于创建条件块with
用于指定作用域range
,它提供了一种“foreach”类型的循环
除了这些之外,它还提供了一些操作用于声明和使用命名模板段
define
在模板中声明一个新的命名模板template
导入一个命名模板block
声明一种特殊的可填充模板区域
在本节中,我们将讨论 if
、with
和 range
。其他内容将在本指南后面的“命名模板”部分中介绍。
If/Else
我们将要看的第一个控制结构是用于有条件地将文本块包含在模板中。这是 if
/else
块。
条件的基本结构如下所示
{{ if PIPELINE }}
# Do something
{{ else if OTHER PIPELINE }}
# Do something else
{{ else }}
# Default case
{{ end }}
注意,我们现在讨论的是管道,而不是值。这样做是为了明确控制结构可以执行整个管道,而不仅仅是评估一个值。
如果值是以下情况,则管道被评估为false
- 布尔型 false
- 数值零
- 空字符串
nil
(空或空值)- 空集合(
map
、slice
、tuple
、dict
、array
)
在所有其他情况下,条件为真。
让我们在我们的 ConfigMap 中添加一个简单的条件。如果饮料设置为咖啡,我们将添加另一个设置
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 }}
由于我们在上一个例子中注释掉了 drink: coffee
,因此输出不应包含 mug: "true"
标志。但是,如果我们将该行添加到 values.yaml
文件中,则输出应如下所示
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: eyewitness-elk-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
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 }}
最初,这看起来不错。但是,如果我们将其通过模板引擎运行,我们将得到一个不幸的结果
$ helm install --dry-run --debug ./mychart
SERVER: "localhost:44134"
CHART PATH: /Users/mattbutcher/Code/Go/src/helm.sh/helm/_scratch/mychart
Error: YAML parse error on mychart/templates/configmap.yaml: error converting YAML to JSON: yaml: line 9: did not find expected key
发生了什么?由于上面的空格,我们生成了不正确的 YAML。
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: eyewitness-elk-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: "true"
mug
的缩进不正确。让我们简单地将其缩进,然后重新运行
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 }}
当我们发送它时,我们将获得有效的 YAML,但看起来仍然有点奇怪
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: telling-chimp-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: "true"
请注意,我们在 YAML 中收到了一些空行。为什么?当模板引擎运行时,它会删除 {{
和 }}
之间的內容,但它会保留其余空格。
YAML 对空格赋予了意义,因此管理空格变得非常重要。幸运的是,舵图模板有一些工具可以提供帮助。
首先,模板声明的波浪号语法可以使用特殊字符进行修改,以告诉模板引擎去除空格。{{-
(添加了破折号和空格)表示应从左侧去除空格,而 -}}
表示应从右侧去除空格。小心!换行符是空格!
确保
-
和指令的其余部分之间有一个空格。{{- 3 }}
表示“从左侧去除空格并打印 3”,而{{-3 }}
表示“打印 -3”。
使用此语法,我们可以修改模板以去除这些换行符
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: {{ .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 }}
记住这一点,我们可以将我们的模板通过舵图运行并查看结果
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: clunky-cat-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
mug: "true"
小心使用去除修饰符。很容易意外地做这样的事情
food: {{ .Values.favorite.food | upper | quote }}
{{- if eq .Values.favorite.drink "coffee" -}}
mug: "true"
{{- end -}}
这将生成 food: "PIZZA"mug: "true"
,因为它在两侧都去除了换行符。
有关模板中空格控制的详细信息,请参阅 官方 Go 模板文档
最后,有时告诉模板系统如何为您缩进比尝试掌握模板指令的间距更容易。为此,您有时可能会发现使用 indent
函数({{ indent 2 "mug:true" }}
)很有用。
使用 with
修改作用域
下一个要看的控制结构是 with
操作。它控制变量的作用域。请记住,.
是对当前作用域的引用。因此 .Values
告诉模板在当前作用域中查找 Values
对象。
with
的语法类似于简单的 if
语句
{{ with PIPELINE }}
# restricted scope
{{ end }}
作用域可以更改。with
可以允许您将当前作用域(.
)设置为特定对象。例如,我们一直在使用 .Values.favorite
。让我们重写我们的 ConfigMap 以更改 .
作用域以指向 .Values.favorite
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 }}
注意,我们从前面的练习中删除了 if
条件,因为它现在不再需要了 - with
之后的块只有在 PIPELINE
的值不为空时才会执行。
请注意,现在我们可以引用 .drink
和 .food
,而无需限定它们。这是因为 with
语句将 .
设置为指向 .Values.favorite
。在 {{ end }}
之后,.
会重置为其之前的作用域。
但是,请注意!在受限作用域内,您将无法使用 .
访问父作用域中的其他对象。例如,这将失败
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
release: {{ .Release.Name }}
{{- end }}
它将产生错误,因为 Release.Name
不在 .
的受限作用域内。但是,如果我们交换最后两行,一切都将按预期工作,因为作用域在 {{ end }}
之后重置。
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
{{- end }}
release: {{ .Release.Name }}
或者,我们可以使用 $
从父作用域访问对象 Release.Name
。$
在模板执行开始时映射到根作用域,并且在模板执行期间不会改变。以下操作也能正常工作
{{- with .Values.favorite }}
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
release: {{ $.Release.Name }}
{{- end }}
在查看 range
之后,我们将研究模板变量,它提供了解决上述作用域问题的方案之一。
使用 range
操作循环
许多编程语言都支持使用 for
循环、foreach
循环或类似的功能机制进行循环。在舵图模板语言中,遍历集合的方式是使用 range
运算符。
首先,让我们在 values.yaml
文件中添加一个披萨配料列表
favorite:
drink: coffee
food: pizza
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions
现在我们有一个 pizzaToppings
列表(在模板中称为 slice
)。我们可以修改模板,将此列表打印到我们的 ConfigMap 中
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 }}
toppings: |-
{{- range .Values.pizzaToppings }}
- {{ . | title | quote }}
{{- end }}
我们可以使用 $
从父作用域访问列表 Values.pizzaToppings
。$
在模板执行开始时映射到根作用域,并且在模板执行期间不会改变。以下操作也能正常工作
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 }}
toppings: |-
{{- range $.Values.pizzaToppings }}
- {{ . | title | quote }}
{{- end }}
{{- end }}
让我们仔细看看 toppings:
列表。range
函数将“遍历”(迭代)pizzaToppings
列表。但是现在发生了一些有趣的事情。就像 with
设置 .
的作用域一样,range
运算符也是如此。每次循环时,.
都被设置为当前披萨配料。也就是说,第一次,.
被设置为 mushrooms
。第二次迭代,它被设置为 cheese
,依此类推。
我们可以将 .
的值直接发送到管道中,因此当我们执行 {{ . | title | quote }}
时,它会将 .
发送到 title
(标题大小写函数),然后发送到 quote
。如果我们运行此模板,输出将是
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: edgy-dragonfly-configmap
data:
myvalue: "Hello World"
drink: "coffee"
food: "PIZZA"
toppings: |-
- "Mushrooms"
- "Cheese"
- "Peppers"
- "Onions"
现在,在这个例子中,我们做了一些棘手的事情。toppings: |-
行声明了一个多行字符串。因此,我们的配料列表实际上不是一个 YAML 列表。它是一个大字符串。为什么我们要这样做?因为 ConfigMaps data
中的数据由键值对组成,其中键和值都是简单的字符串。要了解原因,请查看 Kubernetes ConfigMap 文档。对我们来说,这个细节并不重要。
YAML 中的
|-
标记占用一个多行字符串。这对于在清单中嵌入大型数据块是一个有用的技术,如这里所示。
有时,能够在模板中快速创建列表并进行迭代非常有用。Helm 模板提供了一个方便的函数:tuple
。在计算机科学中,元组是一个大小固定的类似列表的集合,但可以包含任意数据类型。这大致描述了tuple
的使用方式。
sizes: |-
{{- range tuple "small" "medium" "large" }}
- {{ . }}
{{- end }}
以上将生成以下内容
sizes: |-
- small
- medium
- large
除了列表和元组之外,range
还可以用于迭代具有键和值(如map
或dict
)的集合。我们将在下一节介绍模板变量时学习如何操作。