流程控制

控制结构(在模板术语中称为“操作”)为模板作者提供了控制模板生成流程的能力。舵图模板语言提供了以下控制结构

  • if/else 用于创建条件块
  • with 用于指定作用域
  • range,它提供了一种“foreach”类型的循环

除了这些之外,它还提供了一些操作用于声明和使用命名模板段

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

在本节中,我们将讨论 ifwithrange。其他内容将在本指南后面的“命名模板”部分中介绍。

If/Else

我们将要看的第一个控制结构是用于有条件地将文本块包含在模板中。这是 if/else 块。

条件的基本结构如下所示

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

注意,我们现在讨论的是管道,而不是值。这样做是为了明确控制结构可以执行整个管道,而不仅仅是评估一个值。

如果值是以下情况,则管道被评估为false

  • 布尔型 false
  • 数值零
  • 空字符串
  • nil(空或空值)
  • 空集合(mapslicetupledictarray

在所有其他情况下,条件为真。

让我们在我们的 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还可以用于迭代具有键和值(如mapdict)的集合。我们将在下一节介绍模板变量时学习如何操作。