Templati

La specifica sintassi dei templati proviene dalla libreria standard dei templati del linguaggio Go, in cui Helm è scritto.

La stessa sintassi si usa anche in kubectl, Hugo e tanti altri applicativi scritti in Go.

Non è però necessario conoscere il linguaggio Go per usarla (anche se è un linguaggio di programmazione moderno, performante e bello da usare).

Azioni

Lo switch dentro il templating engine avviene quando si incontrano due graffe aperte, {{. Lo switch fuori dal templating engine avviene quando si incontrano due graffe chiuse, }}.

Ciò che si trova entro le graffe sono azioni, e possono essere comandi, strutture dati, costrutti di controllo, sottotemplati o funzioni.

Un trattino, -, prima o dopo le graffe, separato con spazio dal contenuto, sopprime spazi nel testo risultante. Per esempio:

{{ "Hello" -}} , {{- "World" }}

genera

Hello,World

Informazioni Esterne

Informazioni acquisite nel templating engine dall'ambiente esterno sono rappresentate come .Nome, oppure .Nome,nome oppure .Nome.nome.nome, ecc.

Tutte le informazioni iniziano col carattere punto, .. Per motivi derivanti da Go il primo Nome deve essere con iniziale maiuscola. In Go i simboli che iniziano con la maiuscola sono di visibilità pubblica, non privata.

La struttura è gerarchica e il separatore di gerarchia è il carattere punto, .. Corrispondono alla gerarchia presente nei files Yaml di specifica.

Le informazioni possono provenire dal file values.yaml, tantissimi parametri potenziali, e hanno il nome .Values.xxx.

Possono provenire da informazioni sulla release, col nome .Release.xxx:

  • .Release.Name
  • .Release.Namespace
  • .Release.IsInstall
  • .Release.IsUpgrade
  • .Release.Service - il servizio che genera la release, settato a "Helm" nel nostro caso

Possono provenire dal file Chart.yaml, col nome .Chart.xxx:

  • .Chart.Name
  • .Chart.Version
  • .Chart.AppVersion
  • .Chart.Annotations - una lista di annotazioni in formato key=value

Possono provenire da informazioni ricevute dal cluster Kubernetes, col nome .Capabilities.xxx:

  • .Capabilities.APIVersions
  • .Capabilities.KubeVersion.Version
  • .Capabilities.KubeVersion.Major
  • .Capabilities.KubeVersion.Minor

Possono provenire da files dopo una direttiva di inclusione. col nome .Files.xxx.

Possono riferirsi al template in corso di processamento, col nome .Template.xxx:

  • .Template.Name
  • .Template.BasePath

Pipelines e Tipi

Una pipeline è una sequenza di comandi, funzioni e variabili concatenati, separati dal simbolo di pipe |-

Il funzionamento è molto simile a quello standard di Unix/Linux: l'output del comando precedente la pipe è passato come input al comando seguente. Per esempio:

character: {{ .Values.character | default "Sylvester" | quote }}

Il valore del parametro .Values.character è passato alla funzione default. Questa lo controlla e se è la stringa vuota lo sostituisce con la stringa "Sylvester", quidi lo passa alla funzione quote. Tale funzione racchiude la stringa tra doppi apici.

Attenzione nella specifica di parametri, poichè il linguaggio sottostante Go è fortemente tipizzato e le funzioni lavorano solo su tipi prestabiliti.

Per esempio se .Values.character vale 127, questo è un numero intero; se vale ab112 può venir interpretato come intero esadecimale. Solo se vale "ab112" è una stringa.

Non scordarsi i doppi apici per i valori stringa nei vari files sottoposti al templating engine.

Funzioni

Le funzioni provengono dalla libreria Go chiamata Sprig, e mantenuta a https://github.com/Masterminds/sprig. Sono più di 100.

La documentazione delle funzioni è a https://helm.sh/docs/chart_template_guide/function_list/.

Esempio:

{{- toYaml .Values.podSecurityContext | nindent 8 }}
  • viene preso il parametro podSecurityContext dal file values.yaml (o passato tramite l'opzione --set)
  • vengono tolti eventuali spazi all'inizio, dalla funzione -
  • la stringa risultante viene convertita a formato Yaml, dalla funzione toYaml
  • la funzione nindent indenta la stringa di 8 spazi

Go ha tre tipi di collezioni semplici: array (liste immutabili), slice (liste mutabili) e maps (coppie chiave-valore).

Se la funzione nindent riceve in input una collezione, opera su ogni elemento della collezione su nuova riga.

Metodi

In Go un metodo è una funzione che si applica ad un certo tipo di oggetto.

La notazione è (oggetto).metodo.

Se oggetto è una collezione, metodo si applica ad ogni suo elemento.

Vi sono le funzioni:

  • .Files.Get name - ritorna il contenuto del file il cui nome è name, espresso come percorso relativo alla directory radice del chart. Il contenuto è una slice di caratteri, quella che si chiamerebbe una stringa.
  • .Files.GetBytes name - ritorna il contenuto del file il cui nome è name, espresso come percorso relativo alla directory radice del chart. Il contenuto è una slice di byte, espressa in Go come []byte
  • .Files.Glob pattern - ritorna il contenuto dei file conformi allo schema pattern, come stringa (slice di caratteri). Nel pattern si usano gli stessi simboli ? e * della shell di Unix.

Go tratta normalmente le stringhe usando la codifica Unicode UTF-8. Ciò vuol dire che ogni carattere (in Go chiamato runa) è potenzialmente rappresentato da più bytes. Il Go ha corrispondentemente funzioni che operano su stringhe (lunghezza, sottostringhe, ecc.) e funzioni che operano su bytes (JSON, Base64, ecc.).

Queste tre funzioni soprs ritornano collezioni.

Vi sono dei metodi che si applicano alle collezioni risultanti:

  • .Files.AsConfig - trasforma l'input per renderlo compatibile ad un manifest di ConfigMap
  • .Files.AsSecrets - trasforma l'input per renderlo compatibile ad un manifest di Secrets, codificato Base64
  • .Files.Lines - ritorna il contenuto dell'input come array, compiendo uno split degli elementi col carattere \n

Esempio. La stringa di template:

{{ (.Files.Glob "config/*").AsSecrets | indent 2 }}

potrebbe generare:

  jetpack.ini: ZW5hYmxlZCA9IHRydWU=
  rocket.yaml: ZW5hYmxlZDogdHJ1ZQ==

Funzione lookup

Compie una query al cluster Kubernetes alla ricerca di risorse.

Per esempio il seguente ricerca un D chiamato runner nel namespace anvil, e ne ritorna le annotazioni nella sezione metadati:

{{ (lookup "apps/v1" "Deployment" "anvil" "runner").metadata.annotations }}

Gli argomenti di lookup sono:

  • versione API
  • tipo di oggetto
  • namespace
  • nome dell'oggetto

Nell'esempio seguente viene ritornata la lista di tutti i ConfigMap nel namespace anvil:

{{ (lookup "v1" "ConfigMap" "anvil" "").items }}

La lista è iterabile in un loop.

Naturalmente lookup non funziona in un Dry Run o col comando helm template quando non viene contattato il server Kubernetes.

Costrutto if/else

Con solo la branca if:

{{- if .Values.ingress.enabled -}}
...
{{- end }}

Con anche la branca else:

{{- if .Values.ingress.enabled -}}
...
{{- else -}}
# Ingress not enabled
{{- end }}

and e or

Non sono operatori, ma funzioni che prendono i due argomenti da paragonare.

Esempi:

{{- if and .Values.characters .Values.products -}}
...
{{- end }}
{{- if or (eq .Values.character "Wile E. Coyote") .Values.products -}}
...
{{- end }}

Costrutto with

Simile ad un foreach di altri linguaggi.

Esempio:

{{- with .Values.ingress.annotations }}
annotations:
  {{- toYaml . | nindent 4 }}
{{- end }}

Se ad un'iterazione l'argomento corrente del with è vuoto, il blocco viene saltato e si passa all'argomento successivo.

Variabili

Si possono creare delle variabili. Il loro nome inizia col carattere $.

Hanno un tipo, che è dedotto in fase di assegnazione dal tipo del valore.

L'operatore di creazione ed assegnazione - la prima volta - è :=. Il cambiamento di valore quando la variabile esiste già è =.

Esempi:

{{ $var := .Values.character }}
{{ $var = "Tweety" }}

Lo scopo delle variabili è il templato in cui vivono, dal punto di assegnazione in avanti.

Il riferimento ad una variabile che non esiste è un errore.

Costrutto range

Opera in loop su una collezione, che può essere una lista o ona map (anche chiamata un dict).

Esempio:

Date le collezioni:

# An example list in YAML
characters:
  - Sylvester
  - Tweety
  - Road Runner
  - Wile E. Coyote
# An example map in YAML
products:
  anvil: They ring like a bell
  grease: 50% slippery
  boomerang: Guaranteed to return

Iterando sulla lista si può avere:

characters:
{{- range .Values.characters }}
  - {{ . | quote }}
{{- end }}

che fornisce in output:

characters:
  - "Sylvester"
  - "Tweety"
  - "Road Runner"
  - "Wile E. Coyote"

Il punto, ., è l'argomento corrente del loop di range.

Iterando sul dict si può avere:

products:
{{- range $key, $value := .Values.products }}
  - {{ $key }}: {{ $value | quote }}
{{- end }}

che produce in output:

products:
  - anvil: "They ring like a bell"
  - boomerang: "Guaranteed to return"
  - grease: "50% slippery"

$key e $value sono variabili predefinite per l'utilizzo di dict.

Notare che, derivato dal Go, è possibile la doppia assegnazione: $key, $value := .Values.products.


Template con Nome

E' possibile definire un template dandogli un nome, e così richiamarlo da un altro template.

Esempio.

{{/*
Selector labels
*/}}
{{- define "anvil.selectorLabels" -}}
app.kubernetes.io/name: {{ include "anvil.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
  • {{/* ... */}} racchiudono un commento
  • {{- define "anvil.selectorLabels" -}} è il nome che si da al template corrente. Può essere un nome gerarchico, separato da ., e questo è utile per evitare collizioni di nomi
  • segue il corpo del template
  • {{- end -}} termina la definizione di template

include

Richiama un template specificato, includendolo in quello corrente.

Esempio:

{{- include "anvil.selectorLabels" . | nindent 8 }}
  • il primo argomento è il nome del template chiamato
  • il secondo argomento è un oggetto da passare al template. Il carattere . indica l'oggetto globale, cioè tutti gli oggetti che sono in scopo al punto della chiamata. E' l'uso più comune.
  • l'output del template richiamato è eventualmente, come qui, passato alla pipeline