Керування потоком

Структури керування (називаються "діями" в термінології шаблонів) надають вам, автору шаблонів, можливість контролювати потік генерації шаблону. Мова шаблонів Helm надає такі структури керування:

  • if/else для створення умовних блоків
  • with для визначення області видимості
  • range, який надає цикл "for each"

Окрім цих, є кілька дій для оголошення та використання іменованих сегментів шаблону:

  • define оголошує новий іменований шаблон всередині вашого шаблону
  • template імплементує іменований шаблон
  • block оголошує спеціальний тип заповнювальної області шаблону

У цьому розділі ми розглянемо if, with та range. Інші дії будуть розглянуті в розділі "Іменовані шаблони" пізніше в цьому посібнику.

If/Else

Перша структура керування, яку ми розглянемо, використовується для умовного включення блоків тексту в шаблоні. Це блоки if/else.

Основна структура блоку з умовою виглядає так:

{{ if PIPELINE }}
  # Щось зробити
{{ else if OTHER PIPELINE }}
  # Зробити щось інше
{{ else }}
  # Стандартне значення
{{ end }}

Зверніть увагу, що тепер ми говоримо про пайплайни замість значень. Причина в цьому полягає в тому, щоб уточнити, що структури керування можуть виконувати цілий пайплайн, а не лише оцінювати значення.

Пайплайн вважається хибним, якщо значення є:

  • булевим хибним
  • числовим нулем
  • порожнім рядком
  • nil (порожнім або null)
  • порожньою колекцією (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 надає значення пробілам, тому управління пробілами стає важливим. На щастя, шаблони 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 }}
  # обмежена область видимості
{{ 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 циклів або подібних функціональних механізмів. У мові шаблонів Helm, для перебору колекції використовується оператор range.

Спочатку додамо список інгредієнтів для піци до нашого файлу values.yaml:

favorite:
  drink: coffee
  food: pizza
pizzaToppings:
  - mushrooms
  - cheese
  - peppers
  - onions

Тепер у нас є список (в шаблонах він називається 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) — це список фіксованого розміру, але з довільними типами даних. Це приблизно передає те, як використовується tuple.

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

Вищезазначене створить:

  sizes: |-
    - small
    - medium
    - large    

Окрім списків і кортежів, range можна використовувати для перебору колекцій, які мають ключ і значення (як map або dict). Ми розглянемо, як це зробити в наступному розділі, коли введемо змінні шаблону.