流程控制

控制結構(在範本用語中稱為「動作」)讓您(範本作者)能夠控制範本生成的流程。Helm 的範本語言提供下列控制結構

  • if/else 用於建立條件區塊
  • with 用於指定範圍
  • range 提供「針對每個」樣式的迴圈

除了這些之外,它還提供了一些用於宣告和使用具名範本區段的動作

  • 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(空值或 null)
  • 空集合(mapslicetupledictarray

在所有其他情況下,條件均為 true。

讓我們為 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 會將含義賦予空白字元,因此管理空白字元變得非常重要。幸運的是,Helm 範本有一些工具可以提供幫助。

首先,範本宣告的大括號語法可以使用特殊字元進行修改,以指示範本引擎截斷空白字元。{{-(新增了破折號和空格)表示應截斷左側的空白字元,而 -}} 表示應截斷右側的空白字元。*請小心!換行字元也是空白字元!*

請確定 - 與指示的其餘部分之間有一個空格。{{- 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 }}

請牢記這一點,我們可以透過 Helm 執行範本並查看結果

# 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 條件,因為它現在已經不需要了 - 只有在 PIPELINE 的值不為空時,才會執行 with 之後的區塊。

請注意,現在我們可以在不加上限定詞的情況下參考 .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 迴圈或類似的功能機制進行迴圈。在 Helm 的範本語言中,迭代集合的方法是使用 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"    

現在,在這個例子中,我們做了一些 tricky 的事情。toppings: |- 行聲明了一個多行字符串。因此,我們的配料列表實際上不是 YAML 列表。它是一個大字符串。為什麼要這樣做?因為 ConfigMap data 中的數據是由鍵/值對組成的,其中鍵和值都是簡單的字符串。要理解為什麼會這樣,請查看Kubernetes ConfigMap 文件。不過,對我們來說,這個細節並不重要。

YAML 中的 |- 標記會採用多行字符串。這是在您的 manifests 中嵌入大塊數據的有用技巧,如此處所示。

有時,能夠快速在模板中創建列表,然後遍歷該列表非常有用。 Helm 模板有一個使這項工作變得容易的功能:tuple。在計算機科學中,元組是一個固定大小的列表式集合,但具有任意數據類型。這大致傳達了 tuple 的使用方式。

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

以上將產生以下結果

  sizes: |-
    - small
    - medium
    - large    

除了列表和元組之外,range 還可以用於迭代具有鍵和值的集合(例如 mapdict)。我們將在下一節介紹模板變量時看到如何做到這一點。