_ __ __
___ _ __ (_)/ _|/ _| _ _
/ __| '_ | | |_| |_ _| |_ _| |_
__ |_) | | _| _|_ _|_ _|
|___/ .__/|_|_| |_| |_| |_|
|_|
spiff++
ist eine Abzweigung von spiff, die eine kompatible Erweiterung zu spiff basierend auf der neuesten Version bietet und eine Vielzahl neuer Funktionen bietet, die in spiff noch nicht verfügbar sind. Alle vom ursprünglichen Spiff-Projekt bereitgestellten Korrekturen werden auch in Spiff++ integriert. Da es keinen Weg zurück zur SPIFF-Quellbasis geben wird, wurde ein neues unabhängiges SPIFF++-Repository erstellt, um die Entwicklung von SPIFF++ fortzusetzen.spiff ist ein Befehlszeilentool und ein deklaratives In-Domain-Hybrid-YAML-Templating-System. Während normale Template-Systeme eine Template-Datei verarbeiten, indem sie die Template-Ausdrücke durch Werte aus externen Datenquellen ersetzen, bedeutet „domänenintern“, dass die Template-Engine die Syntax und Struktur der verarbeiteten Vorlage kennt. Daher kann es die Werte für die Vorlagenausdrücke direkt aus dem verarbeiteten Dokument übernehmen, einschließlich der Teile, die durch die Vorlagenausdrücke selbst bezeichnet werden.
Zum Beispiel:
resource :
name : bosh deployment
version : 25
url : (( "http://resource.location/bosh?version=" version ))
description : (( "This document describes a " name " located at " url ))
Anstatt nur externe Wertquellen zu verwenden, bietet spiff einen Zusammenführungsmechanismus, um eine Vorlage mit einer beliebigen Anzahl von Zusammenführungs-Stubs zusammenzuführen, um ein endgültiges Dokument zu erstellen.
Es handelt sich um ein Befehlszeilentool und ein deklaratives YAML-Templating-System, das speziell für die Generierung von Bereitstellungsmanifesten (z. B. BOSH-, Kubernetes- oder Landscaper-Manifeste) entwickelt wurde.
Neben der CLI gibt es eine Golang-Bibliothek, die die Verwendung der Spiff-Vorlagenverarbeitung in jedem GO-Programm (z. B. Landscaper) ermöglicht.
Die Templating-Engine ermöglicht den Zugriff auf das Dateisystem basierend auf einem konfigurierbaren virtuellen Dateisystem oder dem Prozesssystem, um Befehle auszuführen und die Ausgabe in die Vorlagenverarbeitung einzubinden.
Inhalt:
<<if:
<<switch:
<<type:
<<for:
<<merge:
Ausführbare Binärdateien der offiziellen Veröffentlichung können über Github-Versionen für Darwin-, Linux- und PowerPC-Maschinen (und virtuelle Maschinen) heruntergeladen werden.
Einige der Abhängigkeiten von spiff haben sich seit der letzten offiziellen Veröffentlichung geändert und spiff wird nicht aktualisiert, um mit diesen Abhängigkeiten Schritt zu halten. Diese Abhängigkeiten werden entweder behoben oder in die lokale Codebasis kopiert.
spiff merge template.yml [template2.yml ...]
Führen Sie eine Reihe von Vorlagendateien in einem Manifest zusammen und drucken Sie es aus.
Einzelheiten zur Vorlagendatei finden Sie unter „Dynaml-Templating-Sprache“ oder unter „examples/subdir“ für kompliziertere Beispiele.
Beispiel:
spiff merge cf-release/templates/cf-deployment.yml my-cloud-stub.yml
Es ist möglich, eine Datei aus der Standardeingabe zu lesen, indem Sie den Dateinamen -
verwenden. Es darf nur einmal verwendet werden. Dies ermöglicht die Verwendung von spiff als Teil einer Pipeline, um nur einen einzelnen Stream zu verarbeiten oder einen Stream basierend auf mehreren Vorlagen/Stubs zu verarbeiten.
Die Vorlagendatei (erstes Argument) kann ein Stream mit mehreren Dokumenten sein, der mehrere YAML-Dokumente enthält, die durch eine Zeile getrennt sind, die nur ---
enthält. Jedes YAML-Dokument wird unabhängig mit den angegebenen Stub-Dateien verarbeitet. Das Ergebnis ist der Strom verarbeiteter Dokumente in derselben Reihenfolge. Wenn der Stammknoten eines Dokuments als temporär markiert ist, wird das Dokument aus dem Ausgabestream ausgeschlossen. Dies kann beispielsweise verwendet werden, um Kubernetes -Manifeste zu generieren, die von kubectl
verwendet werden.
Der merge
-Befehl bietet mehrere Optionen:
Die Option --partial
. Wenn diese Option angegeben ist, behandelt spiff unvollständige Ausdrucksauswertungen. Alle Fehler werden ignoriert und die nicht auflösbaren Teile des Yaml-Dokuments werden als Strings zurückgegeben.
Mit der Option --json
erfolgt die Ausgabe im JSON-Format statt im YAML-Format.
Mit der Option --path <path>
kann anstelle des komplett verarbeiteten Dokuments ein verschachtelter Pfad ausgegeben werden.
Handelt es sich bei der Ausgabe um eine Liste, gibt die Option --split
jedes Listenelement als separates Dokument aus. Das Yaml -Format verwendet wie gewohnt ---
als Trennzeile. Das JSON -Format gibt eine Folge von JSON -Dokumenten aus, eines pro Zeile.
Mit --select <field path>
ist es möglich, ein bestimmtes Feld des verarbeiteten Dokuments für die Ausgabe auszuwählen
Mit --evaluate <dynaml expression>
ist es möglich, einen bestimmten dynaml-Ausdruck auf dem verarbeiteten Dokument für die Ausgabe auszuwerten. Der Ausdruck wird ausgewertet, bevor der Auswahlpfad angewendet wird, der dann das Auswertungsergebnis bearbeitet.
Die Option --state <path>
ermöglicht die Statusunterstützung von spiff . Wenn die angegebene Datei vorhanden ist, wird sie an die erste Stelle der konfigurierten Stub-Liste gesetzt. Für die angegebene Datei wird sie an die erste Stelle der konfigurierten Stub-Liste für die Zusammenführungsverarbeitung gesetzt. Zusätzlich zur Ausgabe des verarbeiteten Dokuments wird dieses nach Knoten gefiltert, die mit dem &state
-Marker gekennzeichnet sind. Dieses gefilterte Dokument wird dann unter der angegebenen Datei gespeichert, wobei die alte Statusdatei mit dem Suffix .bak
gespeichert wird. Dies kann zusammen mit einer manuellen Zusammenführung verwendet werden, wie sie von der Landesbibliothek angeboten wird.
Mit der Option --bindings <path>
kann eine Yaml-Datei angegeben werden, deren Inhalt zum Aufbau zusätzlicher Bindungen für die Verarbeitung verwendet wird. Das Yaml-Dokument muss aus einer Karte bestehen. Jeder Schlüssel dient als zusätzliche Bindung. Das Bindungsdokument wird nicht verarbeitet, die Werte werden wie definiert verwendet.
Mit der Option --tag <tag>:<path>
kann eine Yaml-Datei angegeben werden, deren Inhalt als Wert für ein vordefiniertes globales Tag verwendet wird (siehe Tags). Auf Tags kann durch Referenzausdrücke der Form <tag>::<ref>
zugegriffen werden. Im Gegensatz zu Bindungen konkurriert mit Tags versehener Inhalt nicht mit den Knoten im Dokument, sondern verwendet einen anderen Referenznamensraum.
Mit der Option --define <key>=<value>
(Kurzschrift -D
) können zusätzliche Bindungswerte in der Befehlszeile angegeben werden, die Bindungswerte aus der Bindungsdatei überschreiben. Die Option kann mehrmals vorkommen.
Wenn der Schlüssel Punkte ( .
) enthält, wird er als Pfadausdruck zur Beschreibung von Feldern in tiefen Kartenwerten interpretiert. Ein Punkt (und ein vor einem Punkt) kann durch
maskiert werden, um ihn im Feldnamen zu behalten.
Die Option --preserve-escapes
behält das Escapezeichen für Dynaml-Ausdrücke und List/Map-Merge-Direktiven bei. Diese Option kann genutzt werden, wenn weitere Bearbeitungsschritte eines Bearbeitungsergebnisses mit spiff vorgesehen sind.
Die Option --preserve-temporary
behält die im endgültigen Dokument als temporär markierten Felder bei.
Die Option --features=<featurelist>
aktiviert diese gegebenen Funktionen. Neue Funktionen, die mit dem alten Verhalten nicht kompatibel sind, müssen explizit aktiviert werden. Typischerweise unterbrechen diese Funktionen nicht das übliche Verhalten, sondern führen eine spezielle Interpretation für Yaml-Werte ein, die zuvor als reguläre Werte verwendet wurden.
Die Ordnerbibliotheken bieten einige nützliche Hilfsbibliotheken. Sie können auch als Beispiel für die Leistungsfähigkeit dieser Templating-Engine dienen.
spiff diff manifest.yml other-manifest.yml
Zeigen Sie strukturelle Unterschiede zwischen zwei Bereitstellungsmanifesten an. Hier werden auch Streams mit mehreren Dokumenten unterstützt. Um keinen Unterschied anzuzeigen, muss die Anzahl der Dokumente in beiden Streams identisch sein und jedes Dokument im ersten Stream darf keinen Unterschied zum Dokument mit demselben Index im zweiten Stream aufweisen. Gefundene Unterschiede werden für jedes Dokument separat angezeigt.
Im Gegensatz zu einfachen Diffing-Tools und sogar bosh diff
verfügt dieser Befehl über semantische Kenntnisse eines Bereitstellungsmanifests und ist nicht nur textbasiert. Wenn beispielsweise zwei Manifeste identisch sind, außer dass einige Jobs in unterschiedlicher Reihenfolge aufgeführt sind, erkennt spiff diff
dies, da die Reihenfolge der Jobs in einem Manifest von Bedeutung ist. Wenn sich andererseits zwei Manifeste beispielsweise nur in der Reihenfolge ihrer Ressourcenpools unterscheiden, wird diff zurückgegeben und leer, da die Reihenfolge der Ressourcenpools für eine Bereitstellung eigentlich keine Rolle spielt.
Auch im Gegensatz zu bosh diff
ändert dieser Befehl keine der beiden Dateien.
Es dient dazu, Unterschiede zwischen einer Bereitstellung und der nächsten zu überprüfen.
Typischer Fluss:
$ spiff merge template.yml [templates...] > deployment.yml
$ bosh download manifest [deployment] current.yml
$ spiff diff deployment.yml current.yml
$ bosh deployment deployment.yml
$ bosh deploy
spiff convert --json manifest.yml
Der Unterbefehl convert
kann zum Konvertieren von Eingabedateien in JSON oder einfach zum Normalisieren der Reihenfolge der Felder verwendet werden. Verfügbare Optionen sind --json
, --path
, --split
oder --select
entsprechend ihrer Bedeutung für den merge
Unterbefehl.
spiff encrypt secret.yaml
Der Unterbefehl encrypt
kann zum Verschlüsseln oder Entschlüsseln von Daten gemäß der Funktion encrypt
dynaml“ verwendet werden. Das Passwort kann als zweites Argument angegeben werden oder wird der Umgebungsvariablen SPIFF_ENCRYPTION_KEY
entnommen. Das letzte Argument kann zur Übergabe der Verschlüsselungsmethode verwendet werden (siehe encrypt
).
Die Daten werden aus der angegebenen Datei entnommen. Wenn -
angegeben ist, wird es von stdin gelesen.
Wenn die Option -d
angegeben ist, werden die Daten entschlüsselt, andernfalls werden die Daten als Yaml-Dokument gelesen und das verschlüsselte Ergebnis ausgegeben.
Neue Funktionen, die mit dem alten Verhalten nicht kompatibel sind, müssen explizit aktiviert werden. Typischerweise stören diese Funktionen nicht das allgemeine Verhalten, sondern führen eine dedizierte Interpretation für Yaml-Werte ein, die zuvor als reguläre Werte verwendet wurden und daher bestehende Anwendungsfälle stören können.
Die folgenden Feature-Flags werden derzeit unterstützt:
Besonderheit | Seit | Zustand | Bedeutung |
---|---|---|---|
interpolation | 1.7.0-beta-1 | Alpha | Dynaml als Teil von Yaml-Strings |
control | 1.7.0-beta-4 | Alpha | Yaml-basierte Kontrollstrukturen |
Aktive Feature-Flags können mit der Dynaml -Funktion features()
als Liste von Strings abgefragt werden. Wenn diese Funktion mit einem String-Argument aufgerufen wird, gibt sie zurück, ob die angegebene Funktion derzeit aktiviert ist.
Features können über die Befehlszeile mit der Option --features
, über die Go-Bibliothek mit der Funktion WithFeatures
oder allgemein durch Setzen der Umgebungsvariablen SPIFF_FEATURES
auf eine Feature-Liste aktiviert werden. Diese Einstellung wird immer als Standard verwendet. Durch die Verwendung der Plain()
-Spiff-Einstellungen aus der Go-Bibliothek werden alle Umgebungsvariablen ignoriert.
Eine Funktion kann namentlich oder mit dem Präfix no
angegeben werden, um sie zu deaktivieren.
Der Bibliotheksordner enthält einige nützliche Spiff- Vorlagenbibliotheken. Hierbei handelt es sich im Grunde nur um Stubs, die zur Liste der Zusammenführungsdateien hinzugefügt werden, um die Hilfsfunktionen für die Zusammenführungsverarbeitung bereitzustellen.
Spiff verwendet eine deklarative, logikfreie Vorlagensprache namens „dynaml“ (dynamisches Yaml).
Jeder Dynaml-Knoten wird garantiert in einen YAML-Knoten aufgelöst. Es handelt sich nicht um eine String-Interpolation. Dadurch müssen Entwickler nicht darüber nachdenken, wie ein Wert in der resultierenden Vorlage dargestellt wird.
Ein Dynaml-Knoten erscheint in der .yml-Datei als Zeichenfolge, die einen Ausdruck bezeichnet, der von zwei Klammern (( <dynaml> ))
umgeben ist. Sie können als Wert einer Karte oder als Eintrag in einer Liste verwendet werden. Der Ausdruck kann sich über mehrere Zeilen erstrecken. Auf keinen Fall darf der Yaml-String-Wert mit einer neuen Zeile enden (z. B. mit |-
)
Wenn ein in Klammern gesetzter Wert nicht als dynamischer Ausdruck interpretiert und so in der Ausgabe beibehalten werden soll, kann er durch ein Ausrufezeichen direkt nach den öffnenden Klammern maskiert werden.
Beispielsweise wird ((! .field ))
dem Zeichenfolgenwert (( .field ))
und ((!! .field ))
dem Zeichenfolgenwert ((! .field ))
zugeordnet.
Im Folgenden finden Sie eine vollständige Liste der dynamischen Ausdrücke:
(( foo ))
Suchen Sie in der aktuellen Vorlage nach dem nächsten „foo“-Schlüssel (d. h. lexikalischem Gültigkeitsbereich) und fügen Sie ihn ein.
z.B:
fizz :
buzz :
foo : 1
bar : (( foo ))
bar : (( foo ))
foo : 3
bar : (( foo ))
Dieses Beispiel wird wie folgt aufgelöst:
fizz :
buzz :
foo : 1
bar : 1
bar : 3
foo : 3
bar : 3
Folgendes wird nicht aufgelöst, da der Schlüsselname mit dem zusammenzuführenden Wert übereinstimmt:
foo : 1
hi :
foo : (( foo ))
(( foo.bar.[1].baz ))
Suchen Sie nach der nächstgelegenen „foo“-Taste und folgen Sie von dort aus bis zu .bar.[1].baz
.
Ein Pfad ist eine durch Punkte getrennte Abfolge von Schritten. Ein Schritt ist entweder ein Wort für Karten oder in Klammern eingeschlossene Ziffern für die Listenindizierung. Der Index kann negativ sein (ein Minus gefolgt von Ziffern). Negative Indizes werden vom Ende der Liste übernommen (effektiver Index = Index + Länge (Liste)).
Ein nicht auflösbarer Pfad führte zu einem Auswertungsfehler. Wenn zu erwarten ist, dass eine Referenz manchmal nicht bereitgestellt wird, sollte sie in Kombination mit „||“ verwendet werden. (siehe unten), um eine Lösung zu gewährleisten.
Hinweis : Der dynamische Grammatiker wurde überarbeitet, um jetzt die übliche Indexsyntax zu ermöglichen. Anstelle von foo.bar.[1]
Es ist jetzt möglich foo.bar[1]
zu verwenden.
Hinweis : Referenzen befinden sich immer innerhalb der Vorlage oder des Stubs und die Reihenfolge spielt keine Rolle. Sie können auf einen anderen dynamischen Knoten verweisen und davon ausgehen, dass dieser aufgelöst ist. Der Referenzknoten wird dann erst aufgelöst, wenn der abhängige Knoten aufgelöst ist.
z.B:
properties :
foo : (( something.from.the.stub ))
something : (( merge ))
Dies wird gelöst, solange „etwas“ lösbar ist und solange es etwa Folgendes einbringt:
from :
the :
stub : foo
Wenn der Pfad mit einem Punkt ( .
) beginnt, wird der Pfad immer vom Stamm des Dokuments aus ausgewertet. Wenn das Dokumentstammverzeichnis eine Liste ist, wird die erste Kartenebene zum Auflösen des Pfadausdrucks verwendet, wenn dieser mit .__map
beginnt. Dadurch kann vermieden werden, dass der eigene Listenindex (wie .[1].path
) verwendet werden muss, der sich ändern kann, wenn Listeneinträge hinzugefügt werden.
Listeneinträge, die aus einer Karte mit name
bestehen, können direkt über ihren Namenswert als Pfadkomponente angesprochen werden.
Hinweis : Dies funktioniert auch für die absoluten Pfade für Listendokumente.
z.B:
Das Zeitalter von Alice in
list :
- name : alice
age : 25
kann mithilfe des Pfads list.alice.age
anstelle von list[0].age
referenziert werden.
Als Schlüsselfeld wird standardmäßig ein Feld mit dem Namen name
verwendet. Soll ein anderes Feld als Schlüsselfeld verwendet werden, kann es in einem Listeneintrag als Schlüssel gekennzeichnet werden, indem dem Feldnamen das Schlüsselwort key:
. Dieses Schlüsselwort wird durch die Verarbeitung entfernt und ist nicht Teil des endgültigen Verarbeitungsergebnisses.
z.B:
list :
- key:person : alice
age : 25
alice : (( list.alice ))
wird gelöst
list :
- person : alice
age : 25
alice :
person : alice
age : 25
Dieses neue Schlüsselfeld wird auch beim Zusammenführen von Listen beachtet.
Wenn das ausgewählte Schlüsselfeld mit einem !
beginnt , ist die Schlüsselfunktion deaktiviert. Das Ausrufezeichen wird außerdem aus dem effektiven Feldnamen entfernt.
Wenn die Werte für das Schlüsselfeld nicht eindeutig sind, wird es ebenfalls deaktiviert.
(( foo.[bar].baz ))
Suchen Sie nach der nächstgelegenen „foo“-Taste und gehen Sie von dort aus zu den Feldern, die durch die bar
beschrieben werden, und dann zu .baz.
Der Index kann eine ganzzahlige Konstante (ohne Leerzeichen) sein, wie im letzten Abschnitt beschrieben. Es könnte aber auch ein beliebiger dynamischer Ausdruck sein (sogar eine Ganzzahl, aber mit Leerzeichen). Wenn der Ausdruck eine Zeichenfolge ergibt, sucht er nach dem entsprechenden Feld. Wenn der Ausdruck eine Ganzzahl ergibt, wird das Array-Element mit diesem Index angesprochen. Der Punkt ( .
) vor dem Indexoperator ist optional.
z.B:
properties :
name : alice
foo : (( values.[name].bar ))
values :
alice :
bar : 42
Dadurch wird foo
in den Wert 42
aufgelöst. Der dynamische Index kann auch am Ende des Ausdrucks stehen (ohne .bar
).
Im Grunde ist dies die einfachere Art, etwas auszudrücken wie eval("values." name ".bar")
Wenn der Ausdruck eine Liste ergibt, werden die Listenelemente (Zeichenfolgen oder Ganzzahlen) als Pfadelemente für den Zugriff auf tiefere Felder verwendet.
z.B:
properties :
name :
- foo
- bar
foo : (( values.[name] ))
values :
foo :
bar : 42
löst foo
erneut auf den Wert 42
auf.
Hinweis : Der Indexoperator kann auch für das Stammelement ( .[index]
) verwendet werden.
Es ist möglich, mehrere durch Kommas getrennte Indizes für aufeinanderfolgende Listen anzugeben ( foo[0][1]
entspricht `foo[0,1]). In einem solchen Fall dürfen die Indizes nicht erneut aufgelistet werden.
(( list.[1..3] ))
Der Slice-Ausdruck kann verwendet werden, um eine dedizierte Unterliste aus einem Listenausdruck zu extrahieren. Der Bereich start ..
end extrahiert eine Liste der Länge end-start+1 mit den Elementen vom Index start bis end . Wenn der Startindex negativ ist, wird der Slice vom Ende der Liste von length+start bis length+end genommen. Wenn der Endindex kleiner als der Startindex ist, ist das Ergebnis ein leeres Array.
z.B:
list :
- a
- b
- c
foo : (( list.[1..length(list) - 1] ))
Der Start- oder Endindex kann weggelassen werden. Die Auswahl erfolgt dann entsprechend der tatsächlichen Größe der Liste. Daher ist list.[1..length(list)]
äquivalent zu list.[1..]
.
wertet foo
zur Liste aus [b,c]
.
(( 1.2e4 ))
Zahlenliteratur wird für Ganzzahlen und Gleitkommawerte unterstützt.
(( "foo" ))
String-Literal. Alle JSON-String-Kodierungen werden unterstützt (zum Beispiel n
, "
oder uxxxx
).
(( [ 1, 2, 3 ] ))
Listenliteral. Die Listenelemente könnten wiederum Ausdrücke sein. Es gibt ein spezielles Listenliteral [1 .. -1]
, mit dem ein auf- oder absteigender Zahlenbereich in eine Liste aufgelöst werden kann.
z.B:
list : (( [ 1 .. -1 ] ))
Erträge
list :
- 1
- 0
- -1
(( { "alice" = 25 } ))
Das Kartenliteral kann verwendet werden, um Karten als Teil eines dynamischen Ausdrucks zu beschreiben. Sowohl der Schlüssel als auch der Wert können wiederum Ausdrücke sein, wobei der Schlüsselausdruck zu einer Zeichenfolge ausgewertet werden muss. Auf diese Weise ist es möglich, Karten mit nicht statischen Schlüsseln zu erstellen. Der Zuweisungsoperator =
wurde anstelle des regulären Doppelpunkts :
gewählt, der in Yaml verwendet wird, da dies zu Konflikten mit der Yaml-Syntax führen würde.
Ein Kartenliteral kann aus einer beliebigen Anzahl von Feldzuweisungen bestehen, die durch ein Komma ,
getrennt sind.
z.B:
name : peter
age : 23
map : (( { "alice" = {}, name = age } ))
Erträge
name : peter
age : 23
map :
alice : {}
peter : 23
Eine weitere Möglichkeit, Listen basierend auf Ausdrücken zu erstellen, sind die Funktionen makemap
und list_to_map
.
(( ( "alice" = 25 ) alice ))
Jedem Ausdruck kann eine beliebige Anzahl expliziter Bereichsliterale vorangestellt werden. Ein Bereichsliteral beschreibt eine Karte, deren Werte für die relative Referenzauflösung des Ausdrucks verfügbar sind (statischer Bereich). Es erstellt eine zusätzliche lokale Bindung für Vornamen.
Ein Bereichsliteral kann aus einer beliebigen Anzahl von Feldzuweisungen bestehen, die durch ein Komma ,
getrennt sind. Sowohl der Schlüssel als auch der Wert werden durch Ausdrücke angegeben, wobei der Schlüsselausdruck zu einem String ausgewertet werden muss. Alle Ausdrücke werden im nächstäußeren Bereich ausgewertet. Dies bedeutet, dass spätere Einstellungen in einem Bereich keine früheren Einstellungen im selben Bereichsliteral verwenden können.
z.B:
scoped : (( ( "alice" = 25, "bob" = 26 ) alice + bob ))
Erträge
scoped : 51
Ein Feldname kann auch durch ein Symbol ( $
name ) gekennzeichnet werden.
(( foo bar ))
Verkettungsausdruck, der zum Verketten einer Folge dynamischer Ausdrücke verwendet wird.
(( "foo" bar ))
Verkettung (wobei bar ein weiterer dynamischer Ausdruck ist). Beliebige Folgen einfacher Werte (Zeichenfolge, Ganzzahl und boolescher Wert) können verkettet werden, gegeben durch einen beliebigen dynamischen Ausdruck.
z.B:
domain : example.com
uri : (( "https://" domain ))
In diesem Beispiel wird uri
in den Wert "https://example.com"
aufgelöst.
(( [1,2] bar ))
Verkettung von Listen als Ausdruck (wobei bar ein weiterer dynamischer Ausdruck ist). Beliebige Listenfolgen können verkettet werden, gegeben durch einen beliebigen dynamischen Ausdruck.
z.B:
other_ips : [ 10.0.0.2, 10.0.0.3 ]
static_ips : (( ["10.0.1.2","10.0.1.3"] other_ips ))
In diesem Beispiel wird static_ips
in den Wert [ 10.0.1.2, 10.0.1.3, 10.0.0.2, 10.0.0.3 ]
aufgelöst.
Wenn der zweite Ausdruck einen anderen Wert als eine Liste ergibt (Ganzzahl, Boolescher Wert, Zeichenfolge oder Karte), wird der Wert an die erste Liste angehängt.
z.B:
foo : 3
bar : (( [1] 2 foo "alice" ))
ergibt die Liste [ 1, 2, 3, "alice" ]
für bar
.
(( map1 map2 ))
Verkettung von Karten als Ausdruck. Beliebige Sequenzen von Karten können verkettet werden, gegeben durch jeden dynamischen Ausdruck. Dabei werden Einträge zusammengeführt. Einträge mit gleichem Schlüssel werden von links nach rechts überschrieben.
z.B:
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat : (( foo bar ))
Erträge
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat :
alice : 24
bob : 26
paul : 27
(( auto ))
Kontextsensitive automatische Wertberechnung.
Im Attribut „Größe“ eines Ressourcenpools bedeutet dies, dass die Berechnung auf der Grundlage der Gesamtinstanzen aller Jobs erfolgt, die sich als im aktuellen Ressourcenpool befindlich deklarieren.
z.B:
resource_pools :
- name : mypool
size : (( auto ))
jobs :
- name : myjob
resource_pool : mypool
instances : 2
- name : myotherjob
resource_pool : mypool
instances : 3
- name : yetanotherjob
resource_pool : otherpool
instances : 3
In diesem Fall wird die Größe des Ressourcenpools auf „5“ aufgelöst.
(( merge ))
Bringen Sie den aktuellen Pfad aus den Stub-Dateien ein, die zusammengeführt werden.
z.B:
foo :
bar :
baz : (( merge ))
Ich werde versuchen, foo.bar.baz
aus dem ersten Stub oder dem zweiten usw. einzubinden und den Wert vom letzten Stub zurückzugeben, der ihn bereitstellt.
Wenn der entsprechende Wert nicht definiert ist, wird Null zurückgegeben. Dieser hat dann die gleiche Semantik wie Referenzausdrücke; Eine Nullzusammenführung ist eine ungelöste Vorlage. Siehe ||
.
<<: (( merge ))
Zusammenführen von Karten oder Listen mit dem Inhalt desselben Elements, das in einem Stub gefunden wurde.
** Achtung ** Bei dieser Form der merge
besteht ein Kompatibilitätsproblem. In Versionen vor 1.0.8 wurde dieser Ausdruck nie analysiert, nur die Existenz des Schlüssels <<:
war relevant. Daher gibt es häufig Verwendungen von <<: (( merge ))
wobei <<: (( merge || nil ))
gemeint ist. Die erste Variante würde Inhalt in mindestens einem Stub erfordern (wie immer für den Merge-Operator). Jetzt wird dieser Ausdruck korrekt ausgewertet, aber dadurch würden bestehende Manifest-Vorlagensätze beschädigt, die die erste Variante verwenden, aber die zweite bedeuten. Daher wird dieser Fall explizit behandelt, um eine optionale Zusammenführung zu beschreiben. Wenn wirklich eine erforderliche Zusammenführung gemeint ist, muss ein zusätzlicher expliziter Qualifizierer angegeben werden
Hinweis : Anstatt ein <<:
-Einfügefeld zum Platzieren von Zusammenführungsausdrücken zu verwenden, ist es jetzt auch möglich, <<<:
zu verwenden, was die Verwendung regulärer Yaml-Parser für Spiff-ähnliche Yaml-Dokumente ermöglicht. <<:
wird aus Gründen der Abwärtskompatibilität beibehalten. verwendet werden ( (( merge required ))
).
Wenn der Merge-Schlüssel nicht als regulärer Schlüssel statt als Merge-Direktive interpretiert werden soll, kann er durch ein Ausrufezeichen ( !
) maskiert werden.
Beispielsweise ein Kartenschlüssel <<<!
führt zu einer Zeichenfolge mit dem Schlüssel <<<
und <<<!!
führt zu einem String-Schlüssel <<<!
Werte.yml
foo :
a : 1
b : 2
template.yml
foo :
<< : (( merge ))
b : 3
c : 4
spiff merge template.yml values.yml
ergibt:
foo :
a : 1
b : 2
c : 4
Werte.yml
foo :
- 1
- 2
template.yml
foo :
- 3
- << : (( merge ))
- 4
spiff merge template.yml values.yml
ergibt:
foo :
- 3
- 1
- 2
- 4
- <<: (( merge on key ))
spiff
ist in der Lage, Kartenlisten mit einem Schlüsselfeld zusammenzuführen. Diese Listen werden wie Karten behandelt, wobei der Wert des Schlüsselfelds der Schlüssel ist. Standardmäßig wird der name
verwendet. Mit dem Selektor kann jedoch ein beliebiger on
für einen List-Merge-Ausdruck angegeben werden.
z.B:
list :
- << : (( merge on key ))
- key : alice
age : 25
- key : bob
age : 24
verschmolzen mit
list :
- key : alice
age : 20
- key : peter
age : 13
Erträge
list :
- key : peter
age : 13
- key : alice
age : 20
- key : bob
age : 24
Wenn keine Einfügung neuer Einträge gewünscht wird (wie durch den Einfügungszusammenführungsausdruck gefordert), sondern nur das Überschreiben vorhandener Einträge, kann einem vorhandenen Schlüsselfeld das Tag key:
um einen nicht standardmäßigen Schlüsselnamen anzugeben, zum Beispiel - key:key: alice
.
<<: (( merge replace ))
Ersetzt den gesamten Inhalt eines Elements durch den in einem Stub gefundenen Inhalt, anstatt eine umfassende Zusammenführung des vorhandenen Inhalts durchzuführen.
Werte.yml
foo :
a : 1
b : 2
template.yml
foo :
<< : (( merge replace ))
b : 3
c : 4
spiff merge template.yml values.yml
ergibt:
foo :
a : 1
b : 2
Werte.yml
foo :
- 1
- 2
template.yml
foo :
- << : (( merge replace ))
- 3
- 4
spiff merge template.yml values.yml
ergibt:
foo :
- 1
- 2
<<: (( foo ))
Zusammenführung von Karten und Listen, die in derselben Vorlage oder demselben Stub gefunden wurden.
foo :
a : 1
b : 2
bar :
<< : (( foo )) # any dynaml expression
b : 3
ergibt:
foo :
a : 1
b : 2
bar :
a : 1
b : 3
Dieser Ausdruck fügt lediglich neue Einträge zur tatsächlichen Liste hinzu. Vorhandene Einträge werden nicht mit dem durch den Zusammenführungsausdruck beschriebenen Inhalt zusammengeführt.
bar :
- 1
- 2
foo :
- 3
- << : (( bar ))
- 4
ergibt:
bar :
- 1
- 2
foo :
- 3
- 1
- 2
- 4
Ein häufiger Anwendungsfall hierfür ist das Zusammenführen von Listen statischer IPs oder Bereiche in einer Liste von IPs. Eine andere Möglichkeit besteht darin, einen einzelnen Verkettungsausdruck zu verwenden.
<<: (( merge foo ))
Zusammenführung von Karten oder Listen mit dem Inhalt eines beliebigen Elements, das in einem Stub gefunden wird (Umleitungszusammenführung). Es findet keine weitere (tiefe) Zusammenführung mit dem gleichnamigen Element in einem Stub statt. (Deep Merge von Listen erfordert Karten mit name
)
Umleitungszusammenführungen können auch als direkte Feldwerte verwendet werden. Sie können mit ersetzenden Zusammenführungen wie (( merge replace foo ))
kombiniert werden.
Werte.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
template.yml
foo :
<< : (( merge bar))
b : 3
c : 4
spiff merge template.yml values.yml
ergibt:
foo :
a : 1
b : 2
c : 4
Eine andere Möglichkeit, eine Zusammenführung mit einem anderen Element in einem Stub durchzuführen, könnte auch auf herkömmliche Weise erfolgen:
Werte.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
template.yml
bar :
<< : (( merge ))
b : 3
c : 4
foo : (( bar ))
Aber in diesem Szenario führt die Zusammenführung immer noch die tiefe Zusammenführung mit dem ursprünglichen Elementnamen durch. Daher ergibt spiff merge template.yml values.yml
:
bar :
a : 1
b : 2
c : 4
foo :
a : 10
b : 20
c : 4
Werte.yml
foo :
- 10
- 20
bar :
- 1
- 2
template.yml
foo :
- 3
- << : (( merge bar ))
- 4
spiff merge template.yml values.yml
ergibt:
foo :
- 3
- 1
- 2
- 4
<<: (( merge none ))
Wenn die Referenz einer umleitenden Zusammenführung auf die Konstante none
gesetzt ist, wird überhaupt keine Zusammenführung durchgeführt. Dieser Ausdruck ergibt immer den Nullwert.
zB: für
template.yml
map :
<< : (( merge none ))
value : notmerged
Werte.yml
map :
value : merged
spiff merge template.yml values.yml
ergibt:
map :
value : notmerged
Dies kann für die explizite Feldzusammenführung mithilfe der stub
-Funktion verwendet werden, um auf dedizierte Teile von Upstream-Stubs zuzugreifen.
z.B:
template.yml
map :
<< : (( merge none ))
value : (( "alice" "+" stub(map.value) ))
Werte.yml
map :
value : bob
spiff merge template.yml values.yml
ergibt:
test :
value : alice+bob
Dies funktioniert auch für dedizierte Felder:
template.yml
map :
value : (( merge none // "alice" "+" stub() ))
Werte.yml
map :
value : bob
spiff merge template.yml values.yml
ergibt:
test :
value : alice+bob
(( a || b ))
Verwendet a oder b, wenn a nicht aufgelöst werden kann.
z.B:
foo :
bar :
- name : some
- name : complicated
- name : structure
mything :
complicated_structure : (( merge || foo.bar ))
Dies wird versuchen, mything.complicated_structure
einzubinden, oder, wenn es nicht eingebunden werden kann, die in foo.bar
angegebene Standardeinstellung verwenden.
Der Operator //
prüft zusätzlich, ob a
zu einem gültigen Wert (ungleich ~
) aufgelöst werden kann.
(( 1 + 2 * foo ))
Mit dynamischen Ausdrücken können arithmetische Ganzzahl- und Gleitkommaberechnungen ausgeführt werden. Unterstützte Operationen sind +
, -
, *
und /
. Der Modulo-Operator ( %
) unterstützt nur ganzzahlige Operanden.
z.B:
Werte.yml
foo : 3
bar : (( 1 + 2 * foo ))
spiff merge values.yml
ergibt 7
für bar
. Dies kann mit Verkettungen kombiniert werden (Berechnung hat höhere Priorität als Verkettung in dynamischen Ausdrücken):
foo : 3
bar : (( foo " times 2 yields " 2 * foo ))
Das Ergebnis ist die Zeichenfolge 3 times 2 yields 6
.
(( "10.10.10.10" - 11 ))
Neben der Arithmetik mit ganzen Zahlen ist es auch möglich, Addition und Subtraktion mit IP-Adressen und CIDRS zu verwenden.
z.B:
ip : 10.10.10.10
range : (( ip "-" ip + 247 + 256 * 256 ))
Erträge
ip : 10.10.10.10
range : 10.10.10.10-10.11.11.1
Die Subtraktion funktioniert auch bei zwei IP-Adressen oder CIDRS, um die Anzahl der IP-Adressen zwischen zwei IP-Adressen zu berechnen.
z.B:
diff : (( 10.0.1.0 - 10.0.0.1 + 1 ))
ergibt den Wert 256. IP-Adresskonstanten können direkt in dynamischen Ausdrücken verwendet werden. Sie werden implizit in Zeichenfolgen und zurück in IP-Adressen konvertiert, wenn dies für einen Vorgang erforderlich ist.
Multiplikation und Division können verwendet werden, um IP-Bereichsverschiebungen auf CIDRs zu verarbeiten. Mit der Division kann ein Netzwerk aufgeteilt werden. Die Netzwerkgröße wird erhöht, um mindestens eine dedizierte Anzahl von Subnetzen unterhalb des ursprünglichen CIDR zu ermöglichen. Anschließend kann durch Multiplikation das n-te nächste Subnetz derselben Größe ermittelt werden.
z.B:
subnet : (( "10.1.2.1/24" / 12 )) # first subnet CIDR for 16 subnets
next : (( "10.1.2.1/24" / 12 * 2)) # 2nd next (3rd) subnet CIDRS
Erträge
subnet : 10.1.2.0/28
next : 10.1.2.32/28
Zusätzlich gibt es Funktionen, die auf IPv4-CIDRs arbeiten:
cidr : 192.168.0.1/24
range : (( min_ip(cidr) "-" max_ip(cidr) ))
next : (( max_ip(cidr) + 1 ))
num : (( min_ip(cidr) "+" num_ip(cidr) "=" min_ip(cidr) + num_ip(cidr) ))
contains : (( contains_ip(cidr, "192.168.0.2") ))
Erträge
cidr : 192.168.0.1/24
range : 192.168.0.0-192.168.0.255
next : 192.168.1.0
num : 192.168.0.0+256=192.168.1.0
contains : true
(( a > 1 ? foo :bar ))
Dynaml unterstützt die Vergleichsoperatoren <
, <=
, ==
, !=
, >=
und >
. Die Vergleichsoperatoren arbeiten mit ganzzahligen Werten. Die Gleichheitsprüfungen funktionieren auch auf Listen und Karten. Das Ergebnis ist immer ein boolescher Wert. Um eine Bedingung zu negieren, kann der unäre Not-Opertor ( !
) verwendet werden.
Zusätzlich gibt es den ternären Bedingungsoperator ?:
, mit dem Ausdrücke abhängig von einer Bedingung ausgewertet werden können. Der erste Operand wird als Bedingung verwendet. Der Ausdruck wird auf den zweiten Operanden ausgewertet, wenn die Bedingung wahr ist, andernfalls auf den dritten.
z.B:
foo : alice
bar : bob
age : 24
name : (( age > 24 ? foo :bar ))
ergibt den Wert bob
für die Eigenschaft name
.
Ein Ausdruck gilt als false
wenn er Folgendes ergibt
false
Ansonsten gilt es als true
Bemerkung
Die Verwendung des Symbols :
kann mit der Yaml-Syntax kollidieren, wenn der vollständige Ausdruck kein in Anführungszeichen gesetzter Zeichenfolgenwert ist.
Mit den Operatoren -or
und -and
können Vergleichsoperatoren kombiniert werden, um komplexere Bedingungen zu erstellen.
Bemerkung:
Das traditionellere Operatorsymbol ||
(und &&
) können hier nicht verwendet werden, da der Operator ||
existiert bereits in dynaml mit einer anderen Semantik, die für logische Operationen nicht gilt. Der Ausdruck false || true
wird zu false
ausgewertet, da es unabhängig von seinem Wert den ersten Operanden liefert, sofern dieser definiert ist. Um eine größtmögliche Kompatibilität zu gewährleisten, kann dies nicht geändert werden und die bloßen Symbole or
und and
können nicht verwendet werden, da dies die Verkettung von Referenzen mit solchen Namen ungültig machen würde.
(( 5 -or 6 ))
Wenn beide Seiten eines -or
oder -and
Operators ganzzahlige Werte ergeben, wird eine bitweise Operation ausgeführt und das Ergebnis ist wiederum eine ganze Zahl. Daher ergibt der Ausdruck 5 -or 6
7
.
Dynaml unterstützt eine Reihe vordefinierter Funktionen. Eine Funktion heißt im Allgemeinen wie
result : (( functionname(arg, arg, ...) ))
Zusätzliche Funktionen können als Teil des Yaml-Dokuments mithilfe von Lambda-Ausdrücken definiert werden. Der Funktionsname ist dann entweder ein gruppierter Ausdruck oder der Pfad zum Knoten, der den Lambda-Ausdruck hostet.
(( format( "%s %d", alice, 25) ))
Formatieren Sie eine Zeichenfolge basierend auf den von dynamischen Ausdrücken angegebenen Argumenten. Es gibt eine zweite Variante dieser Funktion: error
formatiert eine Fehlermeldung und setzt die Auswertung auf fehlgeschlagen.
(( join( ", ", list) ))
Verknüpfen Sie Listeneinträge oder direkte Werte mithilfe einer bestimmten Trennzeichenfolge mit einem einzelnen Zeichenfolgenwert. Die zu verknüpfenden Argumente können dynamische Ausdrücke sein, die zu Listen ausgewertet werden, deren Werte wiederum Zeichenfolgen oder Ganzzahlen bzw. Zeichenfolgen- oder Ganzzahlwerte sind.
z.B:
alice : alice
list :
- foo
- bar
join : (( join(", ", "bob", list, alice, 10) ))
ergibt den String-Wert bob, foo, bar, alice, 10
für join
.
(( split( ",", string) ))
Teilen Sie eine Zeichenfolge für ein dediziertes Trennzeichen. Das Ergebnis ist eine Liste. Anstelle einer Trennzeichenfolge kann ein ganzzahliger Wert angegeben werden, der die angegebene Zeichenfolge in eine Liste von Zeichenfolgen mit begrenzter Länge aufteilt. Die Länge wird in Runen gezählt, nicht in Bytes.
z.B:
list : (( split("," "alice, bob") ))
limited : (( split(4, "1234567890") ))
Erträge:
list :
- alice
- ' bob '
limited :
- " 1234 "
- " 5678 "
- " 90 "
Ein optionales drittes Argument kann angegeben werden. Es begrenzt die Anzahl der zurückgegebenen Listeneinträge. Der Wert -1 führt zu einer unbegrenzten Listenlänge.
Soll ein regulärer Ausdruck als Trennzeichenfolge verwendet werden, kann die Funktion split_match
verwendet werden.
(( trim(string) ))
Schneiden Sie eine Zeichenfolge oder alle Elemente einer Zeichenfolgenliste ab. Es gibt ein optionales zweites String-Argument. Es kann verwendet werden, um einen Satz von Zeichen anzugeben, die ausgeschnitten werden sollen. Der Standardschnittsatz besteht aus einem Leerzeichen und einem Tabulatorzeichen.
z.B:
list : (( trim(split("," "alice, bob")) ))
Erträge:
list :
- alice
- bob
(( element(list, index) ))
Gibt ein dediziertes Listenelement zurück, das durch seinen Index gegeben ist.
z.B:
list : (( trim(split("," "alice, bob")) ))
elem : (( element(list,1) ))
Erträge:
list :
- alice
- bob
elem : bob
(( element(map, key) ))
Gibt ein dediziertes Kartenfeld zurück, das durch seinen Schlüssel gegeben ist.
map :
alice : 24
bob : 25
elem : (( element(map,"bob") ))
Erträge:
map :
alice : 24
bob : 25
elem : 25
Diese Funktion kann auch Tasten verarbeiten, die Punkte (.) enthalten.
(( compact(list) ))
Filtern Sie eine Liste und lassen Sie leere Einträge weg.
z.B:
list : (( compact(trim(split("," "alice, , bob"))) ))
Erträge:
list :
- alice
- bob
(( uniq(list) ))
Uniq stellt eine Liste ohne Duplikate bereit.
z.B:
list :
- a
- b
- a
- c
- a
- b
- 0
- " 0 "
uniq : (( uniq(list) ))
Erträge für Feld uniq
:
uniq :
- a
- b
- c
- 0
(( contains(list, "foobar") ))
Prüft, ob eine Liste einen dedizierten Wert enthält. Werte können auch Listen oder Karten sein.
z.B:
list :
- foo
- bar
- foobar
contains : (( contains(list, "foobar") ))
Erträge:
list :
- foo
- bar
- foobar
contains : true
Die Funktion contains
auch Arbeiten mit Strings, um nach Unterstrings zu suchen, oder Maps, um nach einem Schlüssel zu suchen. In diesen Fällen muss das Element eine Zeichenfolge sein.
z.B:
contains : (( contains("foobar", "bar") ))
ergibt true
.
(( basename(path) ))
Die Funktion basename
gibt den Namen des letzten Elements eines Pfades zurück. Das Argument kann entweder ein regulärer Pfadname oder eine URL sein.
z.B:
pathbase : (( basename("alice/bob") ))
urlbase : (( basename("http://foobar/alice/bob?any=parameter") ))
Erträge:
pathbase : bob
urlbase : bob
(( dirname(path) ))
Die Funktion dirname
gibt das übergeordnete Verzeichnis eines Pfades zurück. Das Argument kann entweder ein regulärer Pfadname oder eine URL sein.
z.B:
pathbase : (( dirname("alice/bob") ))
urlbase : (( dirname("http://foobar/alice/bob?any=parameter") ))
ergibt:
pathbase : alice
urlbase : /alice
(( parseurl("http://github.com") ))
Diese Funktion analysiert eine URL und liefert eine Karte mit allen Elementen einer URL. Die Felder port
, userinfo
und password
sind optional.
z.B:
url : (( parseurl("https://user:[email protected]:443/mandelsoft/spiff?branch=master&tag=v1#anchor") ))
Erträge:
url :
scheme : https
host : github.com
port : 443
path : /mandelsoft/spiff
fragment : anchor
query : branch=master&tag=v1
values :
branch : [ master ]
tag : [ v1 ]
userinfo :
username : user
password : pass
(( index(list, "foobar") ))
Überprüft, ob eine Liste einen dedizierten Wert enthält, und den Index der ersten Übereinstimmung zurückgibt. Werte können auch Listen oder Karten sein. Wenn kein Eintrag gefunden werden kann, wird -1
zurückgegeben.
z.B:
list :
- foo
- bar
- foobar
index : (( index(list, "foobar") ))
Ausbeuten:
list :
- foo
- bar
- foobar
index : 2
Der index
funktioniert auch auf Zeichenfolgen, um nach Sub -Strings zu suchen.
z.B:
index : (( index("foobar", "bar") ))
ergibt 3
.
(( lastindex(list, "foobar") ))
Die Funktion lastindex
funktioniert wie index
, aber der Index des letzten Vorkommens wird zurückgegeben.
Die sort
kann verwendet werden, um Ganzzahl- oder String -Listen zu sortieren. Der Sortierbetrieb ist stabil.
z.B:
list :
- alice
- foobar
- bob
sorted : (( sort(list) ))
ergibt sorted
- alice
- bob
- foobar
Wenn andere Typen sortiert werden sollten, insbesondere komplexe Typen wie Listen oder Karten oder eine andere Vergleichsregel erforderlich, kann eine Vergleichsfunktion als optionales zweites Argument angegeben werden. Die Vergleichsfunktion muss ein Lambda -Ausdruck sein, der zwei Argumente nimmt. Der Ergebnistyp muss integer
oder bool
sein und angeben, ob a geringer ist als b . Wenn eine Ganzzahl zurückgegeben wird, sollte dies sein
z.B:
list :
- alice
- foobar
- bob
sorted : (( sort(list, |a,b|->length(a) < length(b)) ))
ergibt sorted
- bob
- alice
- foobar
(( replace(string, "foo", "bar") ))
Ersetzen Sie alle Auftreten einer Sub -Zeichenfolge in einer Zeichenfolge durch eine Ersatzzeichenfolge. Mit einem optionalen vierten Ganzzahlargument kann die Anzahl der Substitutionen begrenzt sein (-1 mittlere unbegrenzte).
z.B:
string : (( replace("foobar", "o", "u") ))
ergibt fuubar
.
Wenn ein regulärer Ausdruck als Suchzeichenfolge verwendet werden sollte, kann die Funktion replace_match
verwendet werden. Hier wird die Suchzeichenfolge als regulärer Ausdruck bewertet. Es kann Subausdrücke verfolgen. Diese Übereinstimmungen können in der Ersatzzeichenfolge verwendet werden
z.B:
string : (( replace_match("foobar", "(o*)b", "b${1}") ))
ergibt fbooar
.
Das Ersatzargument könnte auch eine Lambda -Funktion sein. In diesem Fall wird für jede Übereinstimmung die Funktion aufgerufen, um den Ersatzwert zu bestimmen. Das einzelne Eingangsargument ist eine Liste der tatsächlichen Sub -Expression -Übereinstimmungen.
z.B:
string : (( replace_match("foobar-barfoo", "(o*)b", |m|->upper(m.[1]) "b" ) ))
Ergibt fOObar-barfoo
.
(( substr(string, 1, 2) ))
Extrahieren Sie eine Stub -Zeichenfolge aus einer Zeichenfolge, beginnend von einem gegebenen Startindex bis zu einem optionalen Endindex (exklusiv). Wenn kein Endindex angegeben wird, wird der Sub -Struvt bis zum Ende der Zeichenfolge extrahiert. Beide Indizes könnten negativ sein. In diesem Fall werden sie vom Ende der Saite entnommen.
z.B:
string : " foobar "
end1 : (( substr(string,-2) ))
end2 : (( substr(string,3) ))
range : (( substr(string,1,-1) ))
bewertet
string : foobar
end1 : ar
end2 : bar
range : ooba
(( match("(f.*)(b.*)", "xxxfoobar") ))
Gibt die Übereinstimmung eines regulären Ausdrucks für einen bestimmten String -Wert zurück. Die Übereinstimmung ist eine Liste der übereinstimmenden Werte für die im regulären Ausdruck enthaltenen Subausdrücke. Index 0 bezieht sich auf die Übereinstimmung des vollständigen regulären Ausdrucks. Wenn der Zeichenfolgenwert nicht mit einer leeren Liste übereinstimmt, wird zurückgegeben.
z.B:
matches : (( match("(f.*)*(b.*)", "xxxfoobar") ))
Ausbeuten:
matches :
- foobar
- foo
- bar
Ein drittes Argument der Typ -Ganzzahl kann gegeben werden, um ein Multi -Match von maximaler N -Wiederholungen anzufordern. Wenn der Wert negativ ist, werden alle Wiederholungen gemeldet. Das Ergebnis ist eine Liste aller Übereinstimmungen, jeweils im oben beschriebenen Format.
(( keys(map) ))
Bestimmen Sie die sortierte Liste der in einer Karte verwendeten Schlüssel.
z.B:
map :
alice : 25
bob : 25
keys : (( keys(map) ))
Ausbeuten:
map :
alice : 25
bob : 25
keys :
- alice
- bob
(( length(list) ))
Bestimmen Sie die Länge einer Liste, eine Karte oder einen Stringwert.
z.B:
list :
- alice
- bob
length : (( length(list) ))
Ausbeuten:
list :
- alice
- bob
length : 2
(( base64(string) ))
Der base64
erzeugt eine Basis64 -Codierung einer bestimmten Zeichenfolge. base64_decode
decodiert eine base64 codierte Zeichenfolge.
z.B:
base64 : (( base64("test") ))
test : (( base64_decode(base64)))
bewertet
base54 : dGVzdA==
test : test
Ein optionales zweites Argument kann verwendet werden, um die maximale Linienlänge anzugeben. In diesem Fall ist das Ergebnis eine Multi-Line-Zeichenfolge.
(( hash(string) ))
Der Funktion hash
erzeugt verschiedene Arten von Hashes für die angegebene Zeichenfolge. Standardmäßig wird sha256
Hash generiert. Ein optionales zweites Argument gibt den Hash -Typ an. Mögliche Typen sind md4
, md5
, sha1
, sha224
, sha256
, sha384
, sha2512
, sha512/224
oder sha512/256
.
md5
-Hashes können weiterhin durch den veralteten Finctio md5(string)
erzeugt werden.
z.B:
data : alice
hash :
deprecated : (( md5(data) ))
md4 : (( hash(data,"md4") ))
md5 : (( hash(data,"md5") ))
sha1 : (( hash(data,"sha1") ))
sha224 : (( hash(data,"sha224") ))
sha256 : (( hash(data,"sha256") ))
sha384 : (( hash(data,"sha384") ))
sha512 : (( hash(data,"sha512") ))
sha512_224 : (( hash(data,"sha512/224") ))
sha512_256 : (( hash(data,"sha512/256") ))
bewertet
data : alice
hash :
deprecated : 6384e2b2184bcbf58eccf10ca7a6563c
md4 : 616c69636531d6cfe0d16ae931b73c59d7e0c089c0
md5 : 6384e2b2184bcbf58eccf10ca7a6563c
sha1 : 522b276a356bdf39013dfabea2cd43e141ecc9e8
sha224 : 38b7e5d5651aaf85694a7a7c6d5db1275af86a6df93a36b8a4a2e771
sha256 : 2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90
sha384 : 96a5353e625adc003a01bdcd9b21b21189bdd9806851829f45b81d3dfc6721ee21f6e0e98c4dd63bc559f66c7a74233a
sha512 : 408b27d3097eea5a46bf2ab6433a7234a33d5e49957b13ec7acc2ca08e1a13c75272c90c8d3385d47ede5420a7a9623aad817d9f8a70bd100a0acea7400daa59
sha512_224 : c3b8cfaa37ae15922adf3d21606e3a9836ba2a9d7838b040b7c96fd7
sha512_256 : ad0a339b08dc090fe3b16eae376f7e162836e8728da9c45466842e19508d7627
(( bcrypt("password", 10) ))
Die Funktion bcrypt
generiert einen Bcrypt -Passwort -Hash für die angegebene Zeichenfolge mit dem angegebenen Kostenfaktor (standardmäßig bis 10, falls fehlt).
z.B:
hash : (( bcrypt("password", 10) ))
bewertet
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
(( bcrypt_check("password", hash) ))
Die Funktion bcrypt_check
überprüft ein Passwort für einen bestimmten Bcrypt -Hash.
z.B:
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
bewertet
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : true
(( md5crypt("password") ))
Die Funktion md5crypt
generiert einen Apache MD5 verschlüsselten Kennwort -Hash für die angegebene Zeichenfolge.
z.B:
hash : (( md5crypt("password") ))
bewertet
hash : $apr1$3Qc1aanY$16Sb5h7U1QrcqwZbDJIYZ0
(( md5crypt_check("password", hash) ))
Die Funktion md5crypt_check
überprüft ein Kennwort gegen einen bestimmten Apache MD5 verschlüsselt.
z.B:
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
bewertet
hash : $apr1$B77VuUUZ$NkNFhkvXHW8wERSRoi74O1
valid : true
(( decrypt("secret") ))
Diese Funktion kann verwendet werden, um verschlüsselte Geheimnisse in einer SPIFF YAML -Datei zu speichern. Das verarbeitete Ergebnis enthält dann den entschlüsselten Wert. Alle Knotentypen können verschlüsselt und entschlüsselt werden, einschließlich vollständiger Karten und Listen.
Das Passwort für die Entschlüsselung kann entweder als zweites Argument angegeben werden oder (die bevorzugte Weise) kann durch die Umgebungsvariable SPIFF_ENCRYPTION_KEY
angegeben werden.
Ein optionales letzte Argument kann die Verschlüsselungsmethode auswählen. Die einzige bisher unterstützte Methode ist 3DES
. Andere Methoden können für dedizierte SPIFF -Versionen mit der von der Spiff Library angebotenen Verschlüsselungsmethode -Registrierung hinzugefügt werden.
Ein Wert kann durch Verwendung der encrypt("secret")
verschlüsselt werden.
z.B:
password : this a very secret secret and may never be exposed to unauthorized people
encrypted : (( encrypt("spiff is a cool tool", password) ))
decrypted : (( decrypt(encrypted, password) ))
bewertet zu so etwas wie
decrypted : spiff is a cool tool
encrypted : d889f9e4cc7ae13effcbc8bb8cd0c38d1fb2197738444f753c48796d7946083e6639e5a1bf8f77648f2a1ddf37023c65ff57d52d0519d1d92cbcf87d3e263cba
password : this a very secret secret and may never be exposed to unauthorized people
(( rand("[:alnum:]", 10) ))
Die Funktion rand
erzeugt zufällige Werte. Das erste Argument entscheidet, welche Werte angefordert werden. Ohne Argument erzeugt es eine positive Zufallszahl im int64
-Bereich.
Argumentyp | Ergebnis |
---|---|
int | Ganzzahlwert im Bereich [0, n ) für positive n und ( n , 0] für negative n |
bool | boolescher Wert |
Zeichenfolge | Eine Rune -Zeichenfolge, in der sich die Rune im angegebenen Zeichenbereich befindet, kann jede Kombination von Zeichenklassen oder Zeichenbereichen für Regexp verwendet werden. Wenn ein Argument für zusätzliche Länge angegeben ist, hat die resultierende Zeichenfolge die angegebene Länge. |
z.B:
int : (( rand() ))
int10 : (( rand(10) ))
neg10 : (( rand(-10) ))
bool : (( rand(true) ))
string : (( rand("[:alpha:][:digit:]-", 10) ))
upper : (( rand("A-Z", 10) ))
punct : (( rand("[:punct:]", 10) ))
alnum : (( rand("[:alnum:]", 10) ))
bewertet
int : 8037669378456096839
int10 : 7
neg10 : -5
bool : true
string : ghhjAYPMlD
upper : LBZQFRSURL
alnum : 0p6KS7EhAj
punct : ' &{;,^])"(# '
(( type(foobar) ))
Der type
ergibt eine Zeichenfolge, die den Typ des angegebenen Ausdrucks kennzeichnet.
z.B:
template :
<< : (( &template ))
types :
- int : (( type(1) ))
- float : (( type(1.0) ))
- bool : (( type(true) ))
- string : (( type("foobar") ))
- list : (( type([]) ))
- map : (( type({}) ))
- lambda : (( type(|x|->x) ))
- template : (( type(.template) ))
- nil : (( type(~) ))
- undef : (( type(~~) ))
bewertet Typen zu
types :
- int : int
- float : float
- bool : bool
- string : string
- list : list
- map : map
- lambda : lambda
- template : template
(( defined(foobar) ))
Die Funktion defined
überprüft, ob ein Ausdruck erfolgreich bewertet werden kann. Es ergibt den booleschen Wert true
, wenn der Ausdruck bewertet und ansonsten false
werden kann.
z.B:
zero : 0
div_ok : (( defined(1 / zero ) ))
zero_def : (( defined( zero ) ))
null_def : (( defined( null ) ))
bewertet
zero : 0
div_ok : false
zero_def : true
null_def : false
Diese Funktion kann in Kombination des bedingten Operators verwendet werden, um die Ausdrücke in Abhängigkeit von der Auflösung eines anderen Ausdrucks zu bewerten.
(( valid(foobar) ))
Die Funktion valid
überprüft, ob ein Ausdruck erfolgreich bewertet werden kann, und bewertet einen definierten Wert, der nicht gleich nil
entspricht. Es ergibt den booleschen Wert true
, wenn der Ausdruck bewertet und ansonsten false
werden kann.
z.B:
zero : 0
empty :
map : {}
list : []
div_ok : (( valid(1 / zero ) ))
zero_def : (( valid( zero ) ))
null_def : (( valid( ~ ) ))
empty_def : (( valid( empty ) ))
map_def : (( valid( map ) ))
list_def : (( valid( list ) ))
bewertet
zero : 0
empty : null
map : {}
list : []
div_ok : false
zero_def : true
null_def : false
empty_def : false
map_def : true
list_def : true
(( require(foobar) ))
Die Funktion require
einen Fehler, wenn das angegebene Argument undefiniert oder nil
ist, andernfalls ergibt es den angegebenen Wert.
z.B:
foo : ~
bob : (( foo || "default" ))
alice : (( require(foo) || "default" ))
bewertet
foo : ~
bob : ~
alice : default
(( stub(foo.bar) ))
Der stub
ergibt den Wert eines dedizierten Feldes, das in der ersten stromaufwärtigen Stub gefunden wurde, die ihn definiert.
z.B:
template.yml
value : (( stub(foo.bar) ))
mit Stub zusammengeführt
stub.yml
foo :
bar : foobar
bewertet
value : foobar
Das an diese Funktion übergebene Argument muss entweder ein Referenzliteral oder ein Ausdruck sein, der entweder in eine Zeichenfolge bewertet wird, die eine Referenz oder eine String -Liste bezeichnet, die die Liste der Pfadelemente für die Referenz bezeichnet. Wenn kein Argument oder ein undefinierter ( ~~
) angegeben wird, wird der tatsächliche Feldpfad verwendet.
Bitte beachten Sie, dass eine bestimmte alleinige Referenz nicht als Ausdruck bewertet wird. Wenn ihr Wert verwendet werden sollte, muss sie beispielsweise durch Bezeichnung (ref)
oder [] ref
für einen Listenausdruck in einen Ausdruck transformiert werden.
Alternativ könnte der merge
verwendet werden, z. B. merge foo.bar
. Der Unterschied besteht darin, dass stub
nicht verschmilzt, daher wird das Feld weiterhin zusammengeführt (mit dem ursprünglichen Pfad im Dokument).
(( tagdef("tag", value) ))
Die Funktion tagdef
kann verwendet werden, um dynamische Tags zu definieren (siehe Tags). Im Gegensatz zum Tag -Marker ermöglicht diese Funktion den Tag -Namen und seinen beabsichtigten Wert durch einen Ausdruck. Daher kann es verwendet werden, um Elemente wie map
oder sum
zu komponieren, um ein dynamisches Tag mit berechneten Werten zu erstellen.
Ein optionales drittes Argument kann verwendet werden, um den beabsichtigten Bereich ( local
oder global
) anzugeben. Standardmäßig wird ein lokales Tag erstellt. Lokale Tags sind nur auf der tatsächlichen Verarbeitungsebene (Vorlage oder Sub) sichtbar, während globale Tags, sobald definiert, in allen weiteren Verarbeitungsstufen (Stub oder Vorlage) verwendet werden können.
Alternativ kann der Tag -Name mit einem Start ( *
) vorangestellt werden, um ein globales Tag zu deklarieren.
Der angegebene Tag -Wert wird als Ergebnis für die Funktion verwendet.
z.B:
template.yml
value : (( tagdef("tag:alice", 25) ))
alice : (( tag:alice::. ))
bewertet
value : 25
alice : 25
(( eval(foo "." bar ) ))
Bewerten Sie das Bewertungsergebnis eines String -Expression erneut als Dynaml -Expression. Dies kann zum Beispiel verwendet werden, um Indirektionen zu realisieren.
zB: der Ausdruck in
alice :
bob : married
foo : alice
bar : bob
status : (( eval( foo "." bar ) ))
Berechnet den Pfad zu einem Feld, das dann erneut bewertet wird, um den Wert dieses komponierten Feldes zu erhalten:
alice :
bob : married
foo : alice
bar : bob
status : married
(( env("HOME" ) ))
Lesen Sie den Wert einer Umgebungsvariablen, deren Name als Dynaml -Ausdruck angegeben wird. Wenn die Umgebungsvariable nicht festgelegt ist, schlägt die Bewertung fehl.
In einem zweiten Geschmack akzeptiert die Funktion env
mehrere Argumente und/oder Listenargumente, die einer einzigen Liste verbunden sind. Jeder Eintrag in dieser Liste wird als Name einer Umgebungsvariablen verwendet. Das Ergebnis der Funktion ist eine Karte der angegebenen Variablen als YAML -Element. Hiermit werden nicht existierende Umgebungsvariablen weggelassen.
(( parse(yamlorjson) ))
Analysieren Sie eine YAML- oder JSON -Zeichenfolge und geben Sie den Inhalt als YAML -Wert zurück. Es kann daher zur weiteren Dynaml -Bewertung verwendet werden.
z.B:
json : |
{ "alice": 25 }
result : (( parse( json ).alice ))
Ergibt den Wert 25
für das Feld result
.
Der parse
unterstützt ein optionales zweites Argument, den Parse -Modus . Hier sind die gleichen Modi wie für die Lesefunktion möglich. Der Standard -Parsenmodus ist import
, der Inhalt ist nur analysiert und es gibt keine weitere Bewertung in diesem Schritt.
(( asjson(expr) ))
Diese Funktion transformiert einen YAML -Wert, der durch ihr Argument in eine JSON -Zeichenfolge angegeben ist. Die entsprechende Funktion asyaml
ergibt den YAML -Wert als YAML -Dokumentzeichenfolge .
z.B:
data :
alice : 25
mapped :
json : (( asjson(.data) ))
yaml : (( asyaml(.data) ))
beschließt
data :
alice : 25
mapped :
json : ' {"alice":25} '
yaml : |+
alice: 25
(( catch(expr) ))
Diese Funktion führt einen Ausdruck aus und ergibt eine Bewertungsinformationskarte. Es gelingt immer, auch wenn der Ausdruck fehlschlägt. Die Karte enthält die folgenden Felder:
Name | Typ | Bedeutung |
---|---|---|
valid | bool | Ausdruck ist gültig |
error | Zeichenfolge | Der Fehlermeldungstext der Bewertung |
value | beliebig | Der Wert des Ausdrucks, wenn die Bewertung erfolgreich war |
z.B:
data :
fail : (( catch(1 / 0) ))
valid : (( catch( 5 * 5) ))
beschließt
data :
fail :
error : division by zero
valid : false
valid :
error : " "
valid : true
value : 25
(( static_ips(0, 1, 3) ))
Generieren Sie eine Liste statischer IPs für einen Job.
z.B:
jobs :
- name : myjob
instances : 2
networks :
- name : mynetwork
static_ips : (( static_ips(0, 3, 4) ))
Dadurch werden 3 IPs aus mynetwork
Subnetz erstellt und zwei Einträge zurückgegeben, da es nur zwei Instanzen gibt. Die beiden Einträge sind die vom Netzwerk definierten statischen IP -Bereiche der 0. und 3. Offsets aus den statischen IP -Bereichen.
Zum Beispiel die Datei bye.yml :
networks : (( merge ))
jobs :
- name : myjob
instances : 3
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
und Datei Hi.yml :
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
spiff merge bye.yml hi.yml
kehrt zurück
jobs :
- instances : 3
name : myjob
networks :
- name : cf1
static_ips :
- 10.60.3.10
- 10.60.3.13
- 10.60.3.70
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
.
Wenn Bye.yml stattdessen war
networks : (( merge ))
jobs :
- name : myjob
instances : 2
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
spiff merge bye.yml hi.yml
kehrt stattdessen zurück
jobs :
- instances : 2
name : myjob
networks :
- name : cf1
static_ips :
- 10.60.3.10
- 10.60.3.13
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
static_ips
akzeptiert auch Listenargumente, solange alle transitiv enthaltenen Elemente entweder Listen oder Ganzzahlwerte sind. Dies ermöglicht es, die Liste der IPs wie folgt abkürzen zu können:
static_ips: (( static_ips([1..5]) ))
(( ipset(ranges, 3, 3,4,5,6) ))
Während die Funktion static_ips aus historischen Gründen auf der Struktur eines Bosh -Manifests beruht und nur an dedizierten Stellen im Manifest funktioniert, bietet die Funktion ipset eine ähnliche Berechnung, die nur auf ihren Argumenten basiert. Die verfügbaren IP -Bereiche und die erforderliche Anzahl von IPs werden also als Argumente übergeben.
Das erste Argument (Ranges) kann ein einzelner Bereich als einfache Zeichenfolge oder eine Liste von Zeichenfolgen sein. Jede Saite könnte sein
Das zweite Argument gibt die angeforderte Anzahl von IP -Adressen im Ergebnissatz an.
Die zusätzlichen Argumente geben die Indizes der IPS an (ab 0) in den angegebenen Bereichen. Auch hier können Listen von Indizes verwendet werden.
z.B:
ranges :
- 10.0.0.0 - 10.0.0.255
- 10.0.2.0/24
ipset : (( ipset(ranges,3,[256..260]) ))
Löst IPSET auf [ 10.0.2.0, 10.0.2.1, 10.0.2.2 ]
.
Wenn keine IP -Indizes angegeben sind (nur zwei Argumente), werden die IPs ohne Indirektion ab Beginn des ersten Bereichs bis zum Ende des letzten gegebenen Bereichs ausgewählt.
(( list_to_map(list, "key") ))
Eine Liste von Karteneinträgen mit expliziten Namen/Schlüsselfeldern wird einer Karte mit den speziellen Schlüssel zugeordnet. Standardmäßig wird der name
verwendet, der durch das optionale zweite Argument geändert werden kann. Ein ausdrücklich bezeichnetes Schlüsselfeld in der Liste wird ebenfalls berücksichtigt.
z.B:
list :
- key:foo : alice
age : 24
- foo : bob
age : 30
map : (( list_to_map(list) ))
wird zu kartieren
list :
- foo : alice
age : 24
- foo : bob
age : 30
map :
alice :
age : 24
bob :
age : 30
In Kombination mit Vorlagen und Lambda -Ausdrücken kann dies verwendet werden, um Karten mit willkürlich benannten Schlüsselwerten zu generieren, obwohl Dynaml -Ausdrücke für Schlüsselwerte nicht zulässig sind.
(( makemap(fieldlist) ))
In diesem Geschmack erstellt makemap
eine Karte mit Einträgen, die von der angegebenen Feldliste beschrieben werden. Es wird erwartet, dass die Liste Karten mit dem key
und value
der Einträge enthält und dedizierte Karteneinträge beschreibt.
z.B:
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map : (( makemap(list) ))
Erträge
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map :
" 5 " : 25
alice : 24
bob : 25
Wenn der Schlüsselwert ein Boolescher oder eine Ganzzahl ist, wird er einer Zeichenfolge zugeordnet.
(( makemap(key, value) ))
In diesem Geschmack erstellt makemap
eine Karte mit Einträgen, die von den angegebenen Argumentpaaren beschrieben werden. Die Argumente können eine Abfolge von Schlüssel-/Wertenpaaren sein (angegeben durch separate Argumente).
z.B:
map : (( makemap("peter", 23, "paul", 22) ))
Erträge
map :
paul : 22
peter : 23
Im Gegensatz zum vorherigen makemap
-Aroma könnte dieser auch von Kartenliteralen behandelt werden.
(( merge(map1, map2) ))
Neben dem Keyword merge
gibt es auch eine Funktion namens merge
(es muss immer eine Eröffnungsklasse folgen). Es kann verwendet werden, um die Karten des Schadens, die aus dem tatsächlichen Dokument entnommen wurden, analog zum Stub -Merge -Prozess. Wenn die Karten durch Referenzausdrücke angegeben werden, können sie keine Dynaml -Ausdrücke enthalten, da sie vor der Bewertung der Argumente immer im Kontext des tatsächlichen Dokuments bewertet werden.
z.B:
map1 :
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
Löst result
zu
result :
alice : 26
bob : 24 # <---- expression evaluated before mergeing
Alternativ können Kartenvorlagen übergeben werden (ohne Bewertungsoperator!). In diesem Fall werden die Dynaml -Ausdrücke aus der Vorlage bewertet, während die angegebenen Dokumente wie bei regelmäßigen Anrufen der SPIFF -Zusammenführung verschmelzen.
z.B:
map1 :
<< : (( &template ))
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
Löst result
zu
result :
alice : 26
bob : 26
Eine Karte kann auch durch einen Kartenausdruck gegeben werden. Hier ist es möglich, Dynaml -Ausdrücke mit der üblichen Syntax anzugeben:
z.B:
map1 :
alice : 24
bob : 25
map2 :
alice : 26
peter : 8
result : (( merge(map1, map2, { "bob"="(( carl ))", "carl"=100 }) ))
Löst result
zu
result :
alice : 26
bob : 100
Anstelle mehrerer Argumente kann ein einzelnes Listenargument angegeben werden. Die Liste muss die zu verschmolzenen Karten enthalten.
Nested Fusions haben Zugang zu allen äußeren Bindungen. Relative Referenzen werden zunächst im tatsächlichen Dokument durchsucht. Wenn sie dort nicht gefunden werden, werden alle äußeren Bindungen verwendet, um die Referenz zu suchen, von inneren bis äußeren Bindungen. Zusätzlich bietet der Kontext ( __ctx
) ein OUTER
, bei dem es sich um eine Liste aller äußeren Dokumente der Nested -Fusion handelt, die zur Suche nach absoluten Referenzen verwendet werden können.
z.B:
data :
alice :
age : 24
template :
<< : (( &template ))
bob : 25
outer1 : (( __ctx.OUTER.[0].data )) # absolute access to outer context
outer2 : (( data.alice.age )) # relative access to outer binding
sum : (( .bob + .outer2 ))
merged : (( merge(template) ))
Entschlossen, merged
zu
merged :
bob : 25
outer1 :
alice :
age : 24
outer2 : 24
sum : 49
(( intersect(list1, list2) ))
Die Funktion intersect
überschneidet mehrere Listen. Eine Liste kann Einträge jeglicher Art enthalten.
z.B:
list1 :
- - a
- - b
- a
- b
- { a: b }
- { b: c }
- 0
- 1
- " 0 "
- " 1 "
list2 :
- - a
- - c
- a
- c
- { a: b }
- { b: b }
- 0
- 2
- " 0 "
- " 2 "
intersect : (( intersect(list1, list2) ))
Löst sich intersect
auf
intersect :
- - a
- a
- { a: b }
- 0
- " 0 "
(( reverse(list) ))
Die Funktion reverse
umgekehrt die Reihenfolge einer Liste. Die Liste kann Einträge jeglicher Art enthalten.
z.B:
list :
- - a
- b
- { a: b }
- { b: c }
- 0
- 1
reverse : (( reverse(list) ))
Löst reverse
zu
reverse :
- 1
- 0
- { b: c }
- { a: b }
- b
- - a
(( validate(value,"dnsdomain") ))
Die validate
der Funktion validiert einen Ausdruck mit einem Satz von Validatoren. Das erste Argument ist der Wert, um zu validieren, und alle anderen Argumente sind Validatoren, die erfolgreich sein müssen, um den Wert zu akzeptieren. Wenn mindestens ein Validator fehlschlägt, wird eine geeignete Fehlermeldung erstellt, die den Fehlergrund erklärt.
Ein Validator wird durch eine Zeichenfolge oder eine Liste bezeichnet, die den Validator -Typ als Zeichenfolge und seine Argumente enthält. Ein Validator kann mit einer Voraussetzung negiert werden !
in seinem Namen.
Die folgenden Validatoren sind verfügbar:
Typ | Argumente | Bedeutung |
---|---|---|
empty | keiner | leere Liste, Karte oder Zeichenfolge |
dnsdomain | keiner | DNS -Domainname |
wildcarddnsdomain | keiner | Wildcard -DNS -Domainname |
dnslabel | keiner | DNS -Etikett |
dnsname | keiner | DNS -Domäne oder Wildcard -Domäne |
ip | keiner | IP-Adresse |
cidr | keiner | CIDR |
publickey | keiner | öffentlicher Schlüssel im PEM -Format |
privatekey | keiner | Privatschlüssel im PEM -Format |
certificate | keiner | Zertifikat im PEM -Format |
ca | keiner | Zertifikat für ca |
semver | Optionale Liste der Einschränkungen | Validieren Sie die SEMVER -Version gegen Einschränkungen |
type | Liste der akzeptierten Typschlüsseln | Mindestens eine Typ -Taste muss übereinstimmen |
valueset | Listen Sie das Argument mit Werten auf | mögliche Werte |
value oder = | Wert | Überprüfen Sie den speziellen Wert |
gt oder > | Wert | größer als (Zahl/String) |
lt oder < | Wert | Weniger als (Zahl/String) |
ge oder >= | Wert | größer oder gleich (Zahl/String) |
le oder <= | Wert | weniger oder gleich (Zahl/String) |
match oder ~= | regulärer Ausdruck | String -Wert -Matching -regulärer Ausdruck |
list | Optionale Liste der Eintrags -Validatoren | IS LISTE UND ENTWIRTUNGEN GENIGE VALIERATOREN GENUTE |
map | [[<Key Validator>,] <eintrags Validator>] | IS MAP und Schlüssel und Einträge übereinstimmen angegebene Validatoren |
mapfield | <Feldname> [, <Validator>] | Erforderlicher Eintrag in Karte |
optionalfield | <Feldname> [, <Validator>] | Optionaler Eintrag in Karte |
and | Liste der Validatoren | Alle Validatoren müssen Erfolg haben |
or | Liste der Validatoren | Mindestens ein Validator muss Erfolg haben |
not oder ! | Validator | Negieren Sie das Validator -Argument (en) |
Wenn die Validierung erfolgreich ist, wird der Wert zurückgegeben.
z.B:
dnstarget : (( validate("192.168.42.42", [ "or", "ip", "dnsdomain" ]) ))
bewertet
dnstarget : 192.168.42.42
Wenn die Validierung fehlschlägt, wird ein Fehler, der den Fehler erläutert, generiert.
z.B:
dnstarget : (( validate("alice+bob", [ "or", "ip", "dnsdomain" ]) ))
ergibt den folgenden Fehler:
*condition 1 failed: (is no ip address: alice+bob and is no dns domain: [a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')])
Ein Validator könnte auch ein Lambda -Ausdruck sein, der mindestens ein Argument nimmt und einen Booleschen Wert zurückgibt. Auf diese Weise ist es möglich, eigene Validatoren als Teil des YAML -Dokuments bereitzustellen.
z.B:
val : (( validate( 0, |x|-> x > 1 ) ))
Wenn mehr als ein Parameter deklariert wird, müssen die zusätzlichen Argumente als Validator -Argumente angegeben werden. Das erste Argument ist immer der Wert, den man überprüfen muss.
z.B:
val : (( validate( 0, [|x,m|-> x > m, 5] ) ))
Die Lambda -Funktion kann auch eine Liste mit 1, 2 oder 3 Elementen zurückgeben. Dies kann verwendet werden, um geeignete Nachrichten bereitzustellen.
Index | Bedeutung |
---|---|
0 | Der erste Index ist immer das Match -Ergebnis, es muss als boolean bewertbar sein |
1 | Wenn zwei Elemente angegeben sind, ist der zweite Index die Nachricht, die das tatsächliche Ergebnis beschreibt |
2 | Hier verringert Index 1 die Erfolgsnachricht und 2 die Fehlermeldung |
z.B:
val : (( validate( 6, [|x,m|-> [x > m, "is larger than " m, "is less than or equal to " m], 5] ) ))
Nur um zu erwähnen, dass die Validator -Spezifikation in der Linie angegeben werden kann, wie in den obigen Beispielen gezeigt, aber auch als Referenzausdrücke. Das not
and
und or
Validatoren akzeptieren tief verschachtelte Validator -Spezifikationen.
z.B:
dnsrecords :
domain : 1.2.3.4
validator :
- map
- - or # key validator
- dnsdomain
- wildcarddnsdomain
- ip # entry validator
val : (( validate( map, validator) ))
(( check(value,"dnsdomain") ))
Die check
kann verwendet werden, um eine YAML -Struktur mit einem YAML -basierten Wertprüfer anzupassen. Hiermit kann dieselbe Scheckbeschreibung verwendet werden, die bereits für die Validierung beschrieben werden kann. Das Ergebnis des Anrufs ist ein boolescher Wert, der das Match -Ergebnis angibt. Es scheitert nicht, wenn der Scheck fehlschlägt.
(( error("message") ))
Der error
kann verwendet werden, um explizite Bewertungsfehler mit einer dedizierten Nachricht zu verursachen.
Dies kann beispielsweise verwendet werden, um einen komplexen Verarbeitungsfehler auf eine aussagekräftige Nachricht zu reduzieren, indem die Fehlerfunktion als Standard für den potenziell fehlerhaften Ausdruck angemeldet wird.
z.B:
value : (( <some complex potentially failing expression> || error("this was an error my friend") ))
Ein weiteres Szenario könnte darin bestehen, eine deskriptive Nachricht für fehlende erforderliche Felder auszulassen, indem ein Fehlerausdruck als (Standard-) Wert für ein Feld verwendet wird, das in einem stromaufwärts gelegenen Stub definiert werden soll.
Dynaml unterstützt verschiedene mathematische Funktionen:
Rückkehrzahlen: ceil
, floor
, round
und roundtoeven
Rückkehrerschwimmer oder Ganzzahlen: abs
Returning Floats: sin
, cos
, sinh
, cosh
, asin
, acos
, asinh
, acosh
, sqrt
, exp
, log
, log10
,
Dynaml unterstützt verschiedene Typkonvertierungen zwischen integer
, float
, bool
und string
-Werten nach geeigneten Funktionen.
z.B:
value : (( integer("5") ))
Konvertiert eine Zeichenfolge in einen Ganzzahlwert.
Das Konvertieren einer Ganzzahl in eine Zeichenfolge akzeptiert ein optionales zusätzliches Integer -Argument zum Angeben der Basis für die Konvertierung, beispielsweise string(55,2)
führt zu "110111"
. Die Standardbasis beträgt 10. Die Basis muss zwischen 2 und 36 liegen.
SPIFF unterstützt den Zugriff auf Inhalte außerhalb der Vorlage und der Unterdateien. Es ist möglich, Dateien zu lesen, Befehle und Pipelines auszuführen. Alle diese Funktionen existieren in zwei Geschmacksrichtungen.
sync
verwendet wird, die die Vorlagenverarbeitung mit einem dedizierten Zustand (durch externen Inhalt bereitgestellt) synchronisieren soll. Hier wären die Caching -Operationen nicht nützlich, daher gibt es einen zweiten, ungekochten Geschmack. Jede Funktion ist mit dem Suffix _uncached
verfügbar (z. B. read_uncached()
) (( read("file.yml") ))
Lesen Sie eine Datei und geben Sie ihren Inhalt zurück. Es gibt Unterstützung für drei Inhaltstypen: yaml
-Dateien, text
und binary
. Das Lesen im Binärmodus führt zu einer Basis64-Coded Multi-Line-Zeichenfolge.
Wenn das Datei -Suffix .yml
, .yaml
oder .json
ist, wird standardmäßig der YAML -Typ verwendet. Wenn die Datei als text
gelesen werden soll, muss dieser Typ explizit angegeben werden. In allen anderen Fällen ist der Standard text
. Daher muss die Binärdatei (z. B. ein Archiv) dringend das binary
angeben.
Ein optionaler zweiter Parameter kann verwendet werden, um den gewünschten Rückgabetyp explizit zu spezifizieren: yaml
oder text
. Für YAML -Dokumente werden einige addionale Typen unterstützt: multiyaml
, template
, templates
, import
und importmulti
.
Ein YAML -Dokument wird analysiert und der Baum wird zurückgegeben. Auf die Elemente des Baumes kann durch reguläre Dynamlausdrücke zugegriffen werden.
Zusätzlich kann die YAML -Datei wieder Dynamlausdrücke enthalten. Alle eingeschlossenen Dynaml -Ausdrücke werden im Kontext des Leseausdrucks bewertet. Dies bedeutet, dass dieselbe Datei an verschiedenen Stellen in einem YAML -Dokument je nach den verwendeten Dynaml -Ausdrücken zu unterschiedlichen Unterbäumen führen kann.
Wenn auch ein Multi-Dokument-Yaml lesen kann. Wenn der Typ multiyaml
angegeben ist, wird ein Listenknoten mit den YAML -Dokument -Root -Knoten zurückgegeben.
Das YAML- oder JSON -Dokument kann auch als Vorlage gelesen werden, indem die Typ template
angegeben wird. Hier ist das Ergebnis ein Vorlagenwert, der wie reguläre Inline -Vorlagen verwendet werden kann. Wenn templates
angegeben sind, wird ein Multi-Dokument einer Liste von Vorlagen zugeordnet.
Wenn der Leseart auf import
gesetzt ist, wird der Dateiinhalt als YAML -Dokument gelesen und der Stammknoten wird verwendet, um den Ausdruck zu ersetzen. Potentielle Dynaml -Ausdrücke, die im Dokument enthalten sind, werden nicht mit der tatsächlichen Bindung des Ausdrucks zusammen mit dem Leseaufruf bewertet, sondern als Teil der Originaldatei gewesen. Daher kann dieser Modus nur verwendet werden, wenn es keine weitere Verarbeitung des Leseergebnisses gibt oder die gelieferten Werte unverarbeitet sind.
Dies kann zusammen mit einer geketteten Referenz (für die Prüfung (( read(...).selection ))
verwendet werden, um ein dediziertes Fragment des importierten Dokuments zu decken. Dann wird das Evaluatio nur für den ausgewählten Teil durchgeführt. Ausdrücke und Referenzen in den anderen Teilen werden nicht bewertet und überhaupt nicht zu einem Fehler führen.
z.B:
template.yaml
ages :
alice : 25
data : (( read("import.yaml", "import").first ))
Import.yaml
first :
age : (( ages.alice ))
second :
age : (( ages.bob ))
wird nicht scheitern, da der second
Abschnitt niemals bewertet wird.
Dieser Modus sollte mit Vorsicht genommen werden, da er häufig zu unerwarteten Ergebnissen führt.
Mit dem Lesetyp importmulti
kann YAML-Dateien mit Multi-Dokument als Liste von Knoten importiert werden.
Ein Textdokument wird als einzelne Zeichenfolge zurückgegeben.
Es ist auch möglich, binäre Dokumente zu lesen. Der Inhalt kann nicht direkt als Zeichenfolge (oder YAML -Dokument) verwendet werden. Daher muss der Lesemodus binary
angegeben werden. Der Inhalt wird als Base64-codierter Multi-Line-String-Wert zurückgegeben.
(( exec("command", arg1, arg2) ))
Einen Befehl ausführen. Argumente können alle Dynamlausdrücke sein, einschließlich Referenzausdrücke, die auf Listen oder Karten bewertet wurden. Listen oder Karten werden als einzelne Argumente übergeben, die ein YAML -Dokument mit dem angegebenen Fragment enthalten.
Das Ergebnis wird durch Parsen der Standardausgabe des Befehls bestimmt. Es kann sich um ein YAML-Dokument oder ein einzelner Multi-Line-String oder ein ganzzahliger Wert handeln. Ein YAML-Dokument sollte mit dem Dokumentpräfix beginnen ---
. Wenn der Befehl fehlschlägt, wird der Ausdruck als undefiniert gehandhabt.
z.B
arg :
- a
- b
list : (( exec( "echo", arg ) ))
string : (( exec( "echo", arg.[0] ) ))
Erträge
arg :
- a
- b
list :
- a
- b
string : a
Alternativ kann exec
mit einem einzigen Listenargument aufgerufen werden, das die Befehlszeile vollständig beschreibt.
Der gleiche Befehl wird nur einmal ausgeführt, auch wenn er in mehreren Ausdrücken verwendet wird.
(( pipe(data, "command", arg1, arg2) ))
Führen Sie einen Befehl aus und füttern Sie seine Standardeingabe mit dedizierten Daten. Das Befehlsargument muss eine Zeichenfolge sein. Argumente für den Befehl können alle Dynaml -Ausdrücke sein, einschließlich Referenzausdrücke, die auf Listen oder Karten bewertet wurden. Listen oder Karten werden als einzelne Argumente übergeben, die ein YAML -Dokument mit dem angegebenen Fragment enthalten.
Der Eingabestream wird aus den angegebenen Daten generiert. Wenn es sich um einen einfachen Typ handelt, wird seine String -Darstellung verwendet. Andernfalls wird ein YAML -Dokument aus den Eingabedaten generiert. Das Ergebnis wird durch Parsen der Standardausgabe des Befehls bestimmt. Es kann sich um ein YAML-Dokument oder ein einzelner Multi-Line-String oder ein ganzzahliger Wert handeln. Ein YAML-Dokument sollte mit dem Dokumentpräfix beginnen ---
. Wenn der Befehl fehlschlägt, wird der Ausdruck als undefiniert gehandhabt.
z.B
data :
- a
- b
list : (( pipe( data, "tr", "a", "z") ))
Erträge
arg :
- a
- b
list :
- z
- b
Alternativ kann pipe
mit Daten und einem Listenargument aufgerufen werden, das die Befehlszeile vollständig beschreibt.
Der gleiche Befehl wird nur einmal ausgeführt, auch wenn er in mehreren Ausdrücken verwendet wird.
(( write("file.yml", data) ))
Schreiben Sie eine Datei und geben Sie ihren Inhalt zurück. Wenn das Ergebnis als YAML -Dokument analysiert werden kann, wird das Dokument zurückgegeben. Ein optionales 3. Argument kann verwendet werden, um die Schreiboptionen zu übergeben. Die Optionsargumente können eine Ganzzahl sein, die Dateiberechtigungen bezeichnet (Standard ist 0644
) oder eine von der Kommas getrennte Zeichenfolge mit Optionen. Unterstützte Optionen sind
binary
: Daten werden vor dem Schreiben dekodiert0
zeigt einen Oktalwert an. (( tempfile("file.yml", data) ))
Schreiben Sie eine temporäre Datei AA und geben Sie seinen Pfadnamen zurück. Ein optionales 3. Argument kann verwendet werden, um Schreiboptionen zu übergeben. Es verhalten sich im Grunde verhalten wie write
Achtung : Eine temporäre Datei gibt es nur während der Zusammenführung. Es wird danach gelöscht.
Es kann beispielsweise verwendet werden, um ein temporäres Dateiargument für die exec
-Funktion bereitzustellen.
(( lookup_file("file.yml", list) ))
Durchsuchen einer Datei ist eine Liste von Verzeichnissen. Das Ergebnis ist eine Liste vorhandener Dateien. Mit lookup_dir
ist es möglich, stattdessen ein Verzeichnis zu suchen.
Wenn keine vorhandenen Dateien gefunden werden können, wird die leere Liste zurückgegeben.
Es ist möglich, mehrere Liste oder String -Argumente zu übergeben, um den Suchpfad zu komponieren.
(( mkdir("dir", 0755) ))
Erstellen Sie ein Verzeichnis und alle seine Zwischenverzeichnisse, wenn es noch nicht existiert.
Der Berechtigungsteil ist optional (Standard 0755). Der Pfad des Verzeichnisses kann durch Atring wie Wert oder als Liste von Pfadkomponenten angegeben werden.
(( list_files(".") ))
Dateien in einem Verzeichnis auflisten. Das Ergebnis ist eine Liste vorhandener Dateien. Mit list_dirs
ist es stattdessen möglich, Verzeichnisse aufzulisten.
(( archive(files, "tar") ))
Erstellen Sie ein Archiv des angegebenen Typs (Standard ist tar
), das die aufgeführten Dateien enthält. Das Ergebnis ist das base64 codierte Archiv.
Unterstützte Archivtypen sind tar
und targz
.
files
können eine Liste oder Karte von Dateieinträgen sein. Bei einer Karte wird der Kartenschlüssel als Standard für den Dateipfad verwendet. Ein Dateieintrag ist eine Karte mit den folgenden Feldern:
Feld | Typ | Bedeutung |
---|---|---|
path | Zeichenfolge | Optional für Karten, den Dateipfad im Archiv, standardmäßig mit der Kartenschlüssel |
mode | int oder int String | Dateimodus oder Schreiboptionen. Es verhalten sich im Grunde genommen wie das Optionsargument für write . |
data | beliebig | Dateiinhalt, YAML wird als YAML -Dokument untergebracht. Wenn mode den Binärmodus angibt, wird ein String -Wert Basis64 decodiert. |
base64 | Zeichenfolge | Base64 codierte Binärdaten |
z.B:
yaml :
alice : 26
bob : 27
files :
" data/a/test.yaml " :
data : (( yaml ))
" data/b/README.md " :
data : |+
### Test Docu
**Note**: This is a test
archive : (( archive(files,"targz") ))
content : (( split("n", exec_uncached("tar", "-tvf", tempfile(archive,"binary"))) ))
Ausbeuten:
archive : |-
H4sIAAAAAAAA/+zVsQqDMBAG4Mx5igO3gHqJSQS3go7tUHyBqIEKitDEoW9f
dLRDh6KlJd/yb8ll+HOd8SY1qbfOJw8zDmQHiIhayjURcZuIQhOeSZlphVwL
glwsAXvM8mJ23twJ4qfnbB/3I+I4pmboW1uA0LSZmgJETr89VXCUtf9Neq1O
5blKxm6PO972X/FN/7nKVej/EaIogto6D+XUzpQydpm8ZayA+tY76B0YWHYD
DV9CEATBf3kGAAD//5NlAmIADAAA
content :
- -rw-r--r-- 0/0 22 2019-03-18 09:01 data/a/test.yaml
- -rw-r--r-- 0/0 41 2019-03-18 09:01 data/b/README.md
files :
data/a/test.yaml :
data :
alice : 26
bob : 27
data/b/README.md :
data : |+
### Test Docu
**Note**: This is a test
yaml :
alice : 26
bob : 27
Spiff unterstützt die Handhabung semantischer Versionsnamen. Es unterstützt alle Funktionen aus dem Masterminds -Semver -Paket, das Versionen mit oder ohne führende v
akzeptiert.
(( semver("v1.2-beta.1") ))
Überprüfen Sie, ob eine bestimmte Zeichenfolge eine semantische Version ist, und geben Sie ihr normalisiertes Formular zurück (ohne die Nummer des Release v
mit Major-, Minor- und Patch -Version).
z.B:
normalized : (( semver("v1.2-beta.1") ))
beschließt
normalized : 1.2.0-beta.1
(( semverrelease("v1.2.3-beta.1") ))
Geben Sie den Release -Teil einer semantischen Version zurück, die Metadaten weglässt und Informationen vor der Ferse.
z.B:
release : (( semverrelease("v1.2.3-beta.1") ))
beschließt
release : v1.2.3
Wenn ein zusätzliches String -Argument angegeben wird, ersetzt diese Funktion die Veröffentlichung durch die Veröffentlichung der angegebenen semantischen Versionen, die Metadaten- und Vorbereitungsinformationen erhalten.
z.B:
new : (( semverrelease("1.2.3-beta.1", "1.2.1) ))
beschließt
new : 1.2.1-beta.1
(( semvermajor("1.2.3-beta.1") ))
Bestimmen Sie die Hauptversionsnummer der angegebenen semantischen Version. Das Ergebnis ist eine Ganzzahl.
z.B:
major : (( semvermajor("1.2.3-beta.1") ))
beschließt
major : 1
Der Funktion semverincmajor
kann verwendet werden, um die Hauptversionsnummer zu erhöhen und die Nebenversion, Patch -Version und Release -Suffixe zurückzusetzen.
z.B:
new : (( semverincmajor("1.2.3-beta.1") ))
beschließt
new : 2.0.0
(( semverminor("1.2.3-beta.1") ))
Bestimmen Sie die Minor -Versionsnummer der angegebenen semantischen Version. Das Ergebnis ist eine Ganzzahl.
z.B:
minor : (( semverminor("1.2.3-beta.1") ))
beschließt
minor : 2
Mit dem semverincminor
kann die Minorversionsnummer und die Wiedereinstellung der Patch -Version und die Veröffentlichung von Suffixen verwendet werden.
z.B:
new : (( semverincmajor("v1.2.3-beta.1") ))
beschließt
new : v1.3.0
(( semverpatch("1.2.3-beta.1") ))
Bestimmen Sie die Patch -Versionsnummer der angegebenen semantischen Version. Das Ergebnis ist eine Ganzzahl.
z.B:
patch : (( semverpatch("1.2.3-beta.1") ))
beschließt
patch : 3
Mit dem Funktions semverincpatch
kann die Patch -Versionsnummer oder die Wiedergabeduffix zurückgesetzt werden. Wenn es Rlease -Suffixe gibt, werden sie entfernt und die Release -Informationen werden unverändert gehalten, andernfalls wird die Patch -Versionsnummer erhöht.
z.B:
final : (( semverincpatch("1.2.3-beta.1") ))
new : (( semverincpatch(final) ))
beschließt
final : 1.2.3
new : 1.2.4
(( semverprerelease("1.2.3-beta.1") ))
Bestimmen Sie die Vorversion der angegebenen semantischen Version. Das Ergebnis ist eine Zeichenfolge.
z.B:
prerelease : (( semverprerelease("1.2.3-beta.1") ))
beschließt
prerelease : beta.1
Wenn ein zusätzliches String -Argument angegeben ist, wird diese Funktionssätze ersetzt, ersetzt oder löscht (falls auf leerer Zeichenfolge festgelegt) die Vorveröffentlichung
z.B:
new : (( semverprerelease("1.2.3-beta.1", "beta.2) ))
beschließt
new : 1.2.3-beta.2
(( semvermetadata("1.2.3+demo") ))
Bestimmen Sie die Metadaten der angegebenen semantischen Version. Das Ergebnis ist eine Zeichenfolge.
z.B:
metadata : (( semvermetadata("1.2.3+demo") ))
beschließt
metadata : demo
Wenn ein zusätzliches String -Argument angegeben ist, ersetzt oder löscht diese Funktion die Metadaten.
z.B:
new : (( semvermetadata("1.2.3-test", "demo) ))
beschließt
new : 1.2.3+demo
(( semvercmp("1.2.3", 1.2.3-beta.1") ))
Vergleichen Sie zwei semantische Versionen. Eine Vorverständlichkeit ist immer kleiner als die endgültige Veröffentlichung. Das Ergebnis ist eine Ganzzahl mit den folgenden Werten:
Ergebnis | Bedeutung |
---|---|
-1 | Die erste Version ist vor der zweiten Version |
0 | Beide Versionen sind gleich |
1 | Der erste Versuon ist nach dem zweiten |
z.B:
compare : (( semvercmp("1.2.3", "1.2.3-beta.1") ))
beschließt
compare : 1
(( semvermatch("1.2.3", "~1.2") ))
Stimmen Sie der angegebenen semantischen Version gegen eine Liste von Krankenstäben an. Das Ergebnis ist ein Boolescher. Es ist möglich, eine beliebige Anzahl von Versionsbeschränkungen anzugeben. Wenn keine Einschränkung angegeben wird, prüft die Funktion nur, ob die angegebene Zeichenfolge eine semantische Version ist.
z.B:
match : (( semvermatch("1.2.3", "~1.2") ))
beschließt
match : true
Die vollständige Liste der möglichen Einschränkungenspezifikationen finden Sie hier.
(( semversort("1.2.3", "1.2.1") ))
Sortieren Sie eine Liste von Versionen in aufsteigender Reihenfolge. Ein führendes v
ist erhalten.
z.B:
sorted : (( semversort("1.2.3", "1.2.1") ))
beschließt
sorted :
- 1.2.1
- 1.2.3
Die Liste der zu sortierten Versionen kann auch mit einem einzigen Listenargument angegeben werden.
SPIFF unterstützt einige nützliche Funktionen für die Arbeit mit X509 -Zertifikaten und -tasten. Weitere Informationen finden Sie auch im Bereich Nützlicher, um einige Tipps für den Zustand zu finden.
(( x509genkey(spec) ))
Diese Funktion kann verwendet werden, um private RSA- oder ECDSA -Schlüssel zu generieren. Das Ergebnis ist ein PEM -codierter Schlüssel als Multi -Line -String -Wert. Wenn eine Schlüsselgröße (Ganzzahl oder Zeichenfolge) als Argument angegeben wird, wird ein RSA -Schlüssel mit der angegebenen Schlüsselgröße erzeugt (z. B. 2048). Bei einem der Zeichenfolgewerte
Die Funktion generiert einen geeigneten ECDSA -Schlüssel.
z.B:
keys :
key : (( x509genkey(2048) ))
beschließt zu so etwas wie
key : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
(( x509publickey(key) ))
Für einen bestimmten Schlüssel oder ein bestimmtes Zertifikat im PEM-Format (z. B. mit der X509GenKey-Funktion generiert) extrahiert diese Funktion den öffentlichen Schlüssel und gibt es im PEM-Format erneut als Multi-Line-Zeichenfolge zurück.
z.B:
keys :
key : (( x509genkey(2048) ))
public : (( x509publickey(key)
beschließt zu so etwas wie
key : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
public : |+
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rnsCxMx
vSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb325Bf/
...
VzYqyeQyvvRbNe73BXc5temCaQayzsbghkoWK+Wrc33yLsvpeVQBcB93Xhus+Lt1
1lxsoIrQf/HBsiu/5Q3M8L6klxeAUcDbYwIDAQAB
-----END RSA PUBLIC KEY-----
Um einen SSH -öffentlichen Schlüssel zu generieren, kann ein optionales zusätzliches Formatargument auf ssh
eingestellt werden. Das Ergebnis wird dann ein reguläres öffentliches Schlüsselformat sein, das für SSH verwendet werden kann. Das Standardformat ist pem
das das oben gezeigte PEM -Ausgangsformat bereitstellt.
RSA -Tasten sind standardmäßig im PKCS#1 -Format ( RSA PUBLIC KEY
) in PEM untergebracht. Wenn das generische PKIX -Format ( PUBLIC KEY
) erforderlich ist, muss das Formatargument pkix
angegeben werden.
Mit dem Format ssh
kann diese Funktion auch verwendet werden, um einen pEM -formatierten öffentlichen Schlüssel in einen SSH -Schlüssel umzuwandeln.
(( x509cert(spec) ))
Die Funktion x509cert
erstellt lokal signierte Zertifikate, entweder ein selbst signiertes oder ein Zertifikat, das von einer bestimmten CA signiert ist. Es gibt ein PEM-codiertes Zertifikat als Multi-Line-String-Wert zurück.
Der einzelne Spezifikationsparameter enthält eine Karte mit einigen optionalen und nicht optionalen Feldern, mit denen die Zertifikatsinformationen angegeben werden. Es kann ein Inline -Kartenausdruck oder eine Kartenreferenz in den Rest des YAML -Dokuments sein.
Die folgenden Kartenfelder werden beobachtet:
Feldname | Typ | Erforderlich | Bedeutung |
---|---|---|---|
commonName | Zeichenfolge | optional | Feld des gebräuchlichen Namens des Themas |
organization | Zeichenfolge oder Zeichenfolgeliste | optional | Organisationsfeld des Faches |
country | Zeichenfolge oder Zeichenfolgeliste | optional | Länderfeld des Themas |
isCA | bool | optional | CA -Option des Zertifikats |
usage | Zeichenfolge oder Zeichenfolgeliste | erforderlich | Nutzungsschlüssel für das Zertifikat (siehe unten) |
validity | ganze Zahl | optional | Gültigkeitsintervall in Stunden |
validFrom | Zeichenfolge | optional | Startzeit im Format "1. Jan. 01:22:31 2019" |
hosts | Zeichenfolge oder Zeichenfolgeliste | optional | Liste der DNS -Namen oder IP -Adressen |
privateKey | Zeichenfolge | erforderlich oder PublicKey | Privatschlüssel, um das Zertifikat für zu generieren |
publicKey | Zeichenfolge | erforderlich oder privatke | öffentlicher Schlüssel zum Generieren des Zertifikats für |
caCert | Zeichenfolge | optional | Zertifikat zu unterschreiben |
caPrivateKey | Zeichenfolge | optional | Priavte -Schlüssel für caCert |
Für selbstsignierte Zertifikate muss das Feld privateKey
festgelegt werden. publicKey
und die ca
-Felder sollten weggelassen werden. Wenn das caCert
-Feld angegeben ist, ist auch das caKey
Feld erforderlich. Wenn das privateKey
-Feld zusammen mit dem caCert
angegeben ist, wird der öffentliche Schlüssel für das Zertifikat aus dem privaten Schlüssel extrahiert.
Zusätzliche Felder werden stillschweigend ignoriert.
Die folgenden Nutzungsschlüssel werden unterstützt (der Fall wird ignoriert):
Schlüssel | Bedeutung |
---|---|
Signature | x509.Keyusagedigitudesignature |
Commitment | x509.KeyusageContent Commitment |
KeyEncipherment | x509.KeyUSageKeyCipherment |
DataEncipherment | x509.Keyusagedataencipherment |
KeyAgreement | x509.KeyusageKeyAgreement |
CertSign | x509.KeyusageCertsign |
CRLSign | x509.Keyusagecrlsign |
EncipherOnly | x509.KeyusageCipheronon |
DecipherOnly | x509.Keyusagedecipheronly |
Any | x509.extKeyUseAny |
ServerAuth | x509.extKeyUSageServerAuth |
ClientAuth | x509.extKeyusageclientAuth |
codesigning | x509.extKeyUseageCodessigning |
EmailProtection | x509.extKeyUSageEmail -Protektion |
IPSecEndSystem | x509.extKeyUSageIpSecendsystem |
IPSecTunnel | x509.extKeyUSageIpsectunnel |
IPSecUser | x509.extKeyUSageIpSecuser |
TimeStamping | x509.extKeyUSAgetimestamping |
OCSPSigning | x509.extKeyUSageocSpSsigning |
MicrosoftServerGatedCrypto | x509.extKeyusagemicrosoftservergatedCrypto |
NetscapeServerGatedCrypto | x509.extKeyusagenetscapeservergatedCrypto |
MicrosoftCommercialCodeSigning | x509.extKeyusagemicrosoftCommercialCodesigning |
MicrosoftKernelCodeSigning | x509.extKeyusagemicrosoftkernelCodesigning |
z.B:
spec :
<< : (( &local ))
ca :
organization : Mandelsoft
commonName : Uwe Krueger
privateKey : (( data.cakey ))
isCA : true
usage :
- Signature
- KeyEncipherment
data :
cakey : (( x509genkey(2048) ))
cacert : (( x509cert(spec.ca) ))
generiert ein selbstsigniertes Root-Zertifikat und beschließt zu so etwas wie
cakey : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
cacert : |+
-----BEGIN CERTIFICATE-----
MIIDCjCCAfKgAwIBAgIQb5ex4iGfyCcOa1RvnKSkMDANBgkqhkiG9w0BAQsFADAk
MQ8wDQYDVQQKEwZTQVAgU0UxETAPBgNVBAMTCGdhcmRlbmVyMB4XDTE4MTIzMTE0
...
pOUBE3Tgim5rnpa9K9RJ/m8IVqlupcONlxQmP3cCXm/lBEREjODPRNhU11DJwDdJ
5fd+t5SMEit2BvtTNFXLAwz48EKTxsDPdnHgiQKcbIV8NmgUNPHwXaqRMBLqssKl
Cyvds9xGtAtmZRvYNI0=
-----END CERTIFICATE-----
(( x509parsecert(cert) ))
Diese Funktion analysiert ein im PEM -Format angegebenes Zertifikat und gibt eine Karte mit Feldern zurück:
Feldname | Typ | Erforderlich | Bedeutung |
---|---|---|---|
commonName | Zeichenfolge | optional | Feld des gebräuchlichen Namens des Themas |
organization | String -Liste | optional | Organisationsfeld des Faches |
country | String -Liste | optional | Länderfeld des Themas |
isCA | bool | stets | CA -Option des Zertifikats |
usage | String -Liste | stets | Nutzungsschlüssel für das Zertifikat (siehe unten) |
validity | ganze Zahl | stets | Gültigkeitsintervall in Stunden |
validFrom | Zeichenfolge | stets | Startzeit im Format "1. Jan. 01:22:31 2019" |
validUntil | Zeichenfolge | stets | Startzeit im Format "1. Jan. 01:22:31 2019" |
hosts | String -Liste | optional | Liste der DNS -Namen oder IP -Adressen |
dnsNames | String -Liste | optional | Liste der DNS -Namen |
ipAddresses | String -Liste | optional | Liste der IP -Adressen |
publicKey | Zeichenfolge | stets | öffentlicher Schlüssel zum Generieren des Zertifikats für |
z.B:
data :
<< : (( &temporary ))
spec :
commonName : test
organization : org
validity : 100
isCA : true
privateKey : (( gen.key ))
hosts :
- localhost
- 127.0.0.1
usage :
- ServerAuth
- ClientAuth
- CertSign
gen :
key : (( x509genkey() ))
cert : (( x509cert(spec) ))
cert : (( x509parsecert(data.gen.cert) ))
beschließt
cert :
commonName : test
dnsNames :
- localhost
hosts :
- 127.0.0.1
- localhost
ipAddresses :
- 127.0.0.1
isCA : true
organization :
- org
publickey : |+
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA+UIZQUTa/j+WlXC394bccBTltV+Ig3+zB1l4T6Vo36pMBmU4JIkJ
...
TCsrEC5ey0cCeFij2FijOJ5kmm4cK8jpkkb6fLeQhFEt1qf+QqgBw3targ3LnZQf
uE9t5MIR2X9ycCQSDNBxcuafHSwFrVuy7wIDAQAB
-----END RSA PUBLIC KEY-----
usage :
- CertSign
- ServerAuth
- ClientAuth
validFrom : Mar 11 15:34:36 2019
validUntil : Mar 15 19:34:36 2019
validity : 99 # yepp, that's right, there has already time passed since the creation
spiff supports some useful functions to work with wireguard keys. Please refer also to the Useful to Know section to find some tips for providing state.
(( wggenkey() ))
This function can be used generate private wireguard key. The result will base64 encoded.
z.B:
keys :
key : (( wggenkey() ))
resolves to something like
key : WH9xNVJuSuh7sDVIyUAlmxc+woFDJg4QA6tGUVBtGns=
(( wgpublickey(key) ))
For a given key (for example generated with the wggenkey function) this function extracts the public key and returns it again in base64 format-
z.B:
keys :
key : (( wggenkey() ))
public : (( wgpublickey(key)
resolves to something like
key : WH9xNVJuSuh7sDVIyUAlmxc+woFDJg4QA6tGUVBtGns=
public : n405KfwLpfByhU9pOu0A/ENwp0njcEmmQQJvfYHHQ2M=
(( lambda |x|->x ":" port ))
Lambda expressions can be used to define additional anonymous functions. They can be assigned to yaml nodes as values and referenced with path expressions to call the function with approriate arguments in other dynaml expressions. For the final document they are mapped to string values.
There are two forms of lambda expressions. Während
lvalue : (( lambda |x|->x ":" port ))
yields a function taking one argument by directly taking the elements from the dynaml expression,
string : " |x|->x " : " port "
lvalue : (( lambda string ))
evaluates the result of an expression to a function. The expression must evaluate to a function or string. If the expression is evaluated to a string it parses the function from the string.
Since the evaluation result of a lambda expression is a regular value, it can also be passed as argument to function calls and merged as value along stub processing.
A complete example could look like this:
lvalue : (( lambda |x,y|->x + y ))
mod : (( lambda|x,y,m|->(lambda m)(x, y) + 3 ))
value : (( .mod(1,2, lvalue) ))
Erträge
lvalue : lambda |x,y|->x + y
mod : lambda|x,y,m|->(lambda m)(x, y) + 3
value : 6
If a complete expression is a lambda expression the keyword lambda
can be omitted.
Lambda expressions evaluate to lambda values, that are used as final values in yaml documents processed by spiff .
Note : If the final document still contains lambda values, they are transferred to a textual representation. It is not guaranteed that this representation can correctly be parsed again, if the document is re-processed by spiff . Especially for complex scoped and curried functions this is not possible.
Therefore function nodes should always be temporary or local to be available during processing or merging, but being omitted for the final document.
A typical function call uses positional arguments. Here the given arguments satisfy the declared function parameters in the given order. For lambda values it is also possible to use named arguments in the call expression. Here an argument is assigned to a dedicated parameter as declared by the lambda expression. The order of named arguments can be arbitrarily chosen.
z.B:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=1, b=2, a=1) ))
It is also posible to combine named with positional arguments. Hereby the positional arguments must follow the named ones.
z.B:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=1, 1, 2) ))
The same argument MUST NOT be satified by both, a named and a positional argument.
Instead of using the parameter name it is also possible to use the parameter index, instead.
z.B:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(3=1, 1) ))
As such, this feature seems to be quite useless, but it shows its power if combined with optional parameters or currying as shown in the next paragraphs.
A lambda expression might refer to absolute or relative nodes of the actual yaml document of the call. Relative references are evaluated in the context of the function call. daher
lvalue : (( lambda |x,y|->x + y + offset ))
offset : 0
values :
offset : 3
value : (( .lvalue(1,2) ))
yields 6
for values.value
.
Besides the specified parameters, there is an implicit name ( _
), that can be used to refer to the function itself. It can be used to define self recursive function. Together with the logical and conditional operators a fibunacci function can be defined:
fibonacci : (( lambda |x|-> x <= 0 ? 0 :x == 1 ? 1 :_(x - 2) + _( x - 1 ) ))
value : (( .fibonacci(5) ))
yields the value 8
for the value
property.
By default reference expressions in a lambda expression are evaluated in the static scope of the lambda dedinition followed by the static yaml scope of the caller. Absolute references are always evalated in the document scope of the caller.
The name _
can also be used as an anchor to refer to the static definition scope of the lambda expression in the yaml document that was used to define the lambda function. Those references are always interpreted as relative references related to the this static yaml document scope. There is no denotation for accessing the root element of this definition scope.
Relative names can be used to access the static definition scope given inside the dynaml expression (outer scope literals and parameters of outer lambda parameters)
z.B:
env :
func : (( |x|->[ x, scope, _, _.scope ] ))
scope : definition
call :
result : (( env.func("arg") ))
scope : call
yields the result
list:
call :
result :
- arg
- call
- (( lambda|x|->[x, scope, _, _.scope] )) # the lambda expression as lambda value
- definition
This also works across multiple stubs. The definition context is the stub the lambda expression is defined in, even if it is used in stubs down the chain. Therefore it is possible to use references in the lambda expression, not visible at the caller location, they carry the static yaml document scope of their definition with them.
Inner lambda expressions remember the local binding of outer lambda expressions. This can be used to return functions based on arguments of the outer function.
z.B:
mult : (( lambda |x|-> lambda |y|-> x * y ))
mult2 : (( .mult(2) ))
value : (( .mult2(3) ))
yields 6
for property value
.
Trailing parameters may be defaulted in the lambda expression by assigning values in the declaration. Those parameter are then optional, it is not required to specify arguments for those parameters in function calls.
z.B:
mult : (( lambda |x,y=2|-> x * y ))
value : (( .mult(3) ))
yields 6
for property value
.
It is possible to default all parameters of a lambda expression. The function can then be called without arguments. There might be no non-defaulted parameters after a defaulted one.
A call with positional arguments may only omit arguments for optional parameters from right to left. If there should be an explicit argument for the right most parameter, arguments for all parameters must be specified or named arguments must be used. Here the desired optional parameter can explicitly be set prior to the regular positional arguments.
z.B:
func : (( |a,b=1,c=2|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=3, 2) ))
evaluates result
to
result :
a : 2
b : 1
c : 3
The expression for the default does not need to be a constant value or even expression, it might refer to other nodes in the yaml document. The default expression is always evaluated in the scope of the lambda expression declaration at the time the lambda expression is evaluated.
z.B:
stub.yaml
default : 2
mult : (( lambda |x,y=default * 2|-> x * y ))
template.yaml
mult : (( merge ))
scope :
default : 3
value : (( .mult(3) ))
evaluates value
to 12
The last parameter in the parameter list of a lambda expression may be a varargs parameter consuming additional argument in a fnction call. This parameter is always a list of values, one entry per additional argument.
A varargs parameter is denoted by a ...
following the last parameter name.
z.B:
func : (( |a,b...|-> [a] b ))
result : (( .func(1,2,3) ))
yields the list [1, 2, 3]
for property result
.
If no argument is given for the varargs parameter its value is the empty list.
The ...
operator can also be used for inline list expansion.
If a vararg parameter should be set by a named argument its value must be a list.
Using the currying operator ( *(
) a lambda function may be transformed to another function with less parameters by specifying leading argument values.
The result is a new function taking the missing arguments (currying) and using the original function body with a static binding for the specified parameters.
z.B:
mult : (( lambda |x,y|-> x * y ))
mult2 : (( .mult*(2) ))
value : (( .mult2(3) ))
Currying may be combined with defaulted parameters. But the resulting function does not default the leading parameters, it is just a new function with less parameters pinning the specified ones.
If the original function uses a variable argument list, the currying may span any number of the variable argument part, but once at least one such argument is given, the parameter for the variable part is satisfied. It cannot be extended by a function call of the curried function.
z.B:
func : (( |a,b...|->join(a,b) ))
func1 : (( .func*(",","a","b")))
# invalid: (( .func1("c") ))
value : (( .func1() ))
evaluates value
to "a,b"
.
It is also possible to use currying for builtin functions, like join
.
z.B:
stringlist : (( join*(",") ))
value : (( .stringlist("a", "b") ))
evaluates value
to "a,b"
.
There are several builtin functions acting on unevaluated or unevaluatable arguments, like defined
. For these functions currying is not possible.
Using positional arguments currying is only possible from right to left. But currying can also be done for named arguments. Here any parameter combination, regardless of the position in the parameter list, can be preset. The resulting function then has the unsatisfied parameters in their original order. Switching the parameter order is not possible.
z.B:
func : (( |a,b=1,c=2|->{$a=a, $b=b, $c=c } ))
curry : (( .func(c=3, 2) ))
result : (( .curry(5) ))
evalutes result
to
result :
a : 2
b : 5
c : 3
The resulting function keeps the parameter b
. Hereby the default value will be kept. Therefore it can just be called without argument ( .curry()
), which would produce
result :
a : 2
b : 1
c : 3
Aufmerksamkeit :
For compatibility reasons currying is also done, if a lambda function without defaulted parameters is called with less arguments than declared parameters.
This behaviour is deprecated and will be removed in the future. It is replaced by the currying operator.
z.B:
mult : (( lambda |x,y|-> x * y ))
mult2 : (( .mult(2) ))
value : (( .mult2(3) ))
evaluates value
to 6.
(( catch[expr|v,e|->v] ))
This expression evaluates an expression ( expr
) and then executes a lambda function with the evaluation state of the expression. It always succeeds, even if the expression fails. The lambda function may take one or two arguments, the first is always the evaluated value (or nil
in case of an error). The optional second argument gets the error message the evaluation of the expression failed (or nil
otherwise)
The result of the function is the result of the whole expression. If the function fails, the complete expression fails.
z.B:
data :
fail : (( catch[1 / 0|v,e|->{$value=v, $error=e}] ))
valid : (( catch[5 * 5|v,e|->{$value=v, $error=e}] ))
resolves to
data :
fail :
error : division by zero
value : null
valid :
error : null
value : 25
(( sync[expr|v,e|->defined(v.field),v.field|10] ))
If an expression expr
may return different results for different evaluations, it is possible to synchronize the final output with a dedicated condition on the expression value. Such an expression could, for example, be an uncached read
, exec
or pipe
call.
The second element must evaluate to a lambda value, given by either a regular expression or by a lambda literal as shown in the title. It may take one or two arguments, the actual value of the value expression and optionally an error message in case of a failing evaluation. The result of the evaluation of the lamda expression decides whether the state of the evaluation of the value expression is acceptable ( true
) or not ( false
).
If the value is accepted, an optional third expression is used to determine the final result of the sync[]
expression. It might be given as an expression evaluating to a lambda value, or by a comma separated expression using the same binding as the preceeding lambda literal. If not given, the value of the synched expression is returned.
If the value is not acceptable, the evaluation is repeated until a timeout applies. The timeout in seconds is given by an optional fourth expression (default is 5 min). Either the fourth, or the both, the third and the fourth elements may be omitted.
The lambda values might be given as literal, or by expression, leading to the following flavors:
sync[expr|v,e|->cond,value|10]
sync[expr|v,e|->cond|valuelambda|10]
sync[expr|v,e|->cond|v|->value|10]
sync[expr|condlambda|valuelambda|10]
sync[expr|condlambda|v|->value|10]
with or without the timeout expression.
z.B:
data :
alice : 25
result : (( sync[data|v|->defined(v.alice),v.alice] ))
resolves to
data :
alice : 25
result : 25
This example is quite useless, because the sync expression is a constant. It just demonstrates the usage.
Mappings are used to produce a new list from the entries of a list or map , or a new map from entries of a map containing the entries processed by a dynaml expression. The expression is given by a lambda function. There are two basic forms of the mapping function: It can be inlined as in (( map[list|x|->x ":" port] ))
, or it can be determined by a regular dynaml expression evaluating to a lambda function as in (( map[list|mapping.expression))
(here the mapping is taken from the property mapping.expression
, which should hold an approriate lambda function).
The mapping comes in two target flavors: with []
or {}
in the syntax. The first flavor always produces a list from the entries of the given source. The second one takes only a map source and produces a filtered or transformed map .
Additionally the mapping uses three basic mapping behaviours:
map
. Here the result of the lambda function is used as new value to replace the original one. Oderselect
. Here the result of the lambda function is used as a boolean to decide whether the entry should be kept ( true
) or omitted ( false
).sum
. Here always the list flavor is used, but the result type and content is completely determined by the parameterization of the statement by successively aggregating one entry after the other into an arbitrary initial value. Note : The special reference _
is not set for inlined lambda functions as part of the mapping syntax. Therefore the mapping statements (and all other statements using inlined lambda functions as part of their syntax) can be used inside regular lambda functions without hampering the meaning of this special refrence for the surrounding explicit lambda expression.
(( map[list|elem|->dynaml-expr] ))
Execute a mapping expression on members of a list to produce a new (mapped) list. The first expression ( list
) must resolve to a list. The last expression ( x ":" port
) defines the mapping expression used to map all members of the given list. Inside this expression an arbitrarily declared simple reference name (here x
) can be used to access the actually processed list element.
z.B
port : 4711
hosts :
- alice
- bob
mapped : (( map[hosts|x|->x ":" port] ))
Erträge
port : 4711
hosts :
- alice
- bob
mapped :
- alice:4711
- bob:4711
This expression can be combined with others, for example:
port : 4711
list :
- alice
- bob
joined : (( join( ", ", map[list|x|->x ":" port] ) ))
which magically provides a comma separated list of ported hosts:
port : 4711
list :
- alice
- bob
joined : alice:4711, bob:4711
(( map[list|idx,elem|->dynaml-expr] ))
In this variant, the first argument idx
is provided with the index and the second elem
with the value for the index.
z.B
list :
- name : alice
age : 25
- name : bob
age : 24
ages : (( map[list|i,p|->i + 1 ". " p.name " is " p.age ] ))
Erträge
list :
- name : alice
age : 25
- name : bob
age : 24
ages :
- 1. alice is 25
- 2. bob is 24
(( map[map|key,value|->dynaml-expr] ))
Mapping of a map to a list using a mapping expression. The expression may have access to the key and/or the value. If two references are declared, both values are passed to the expression, the first one is provided with the key and the second one with the value for the key. If one reference is declared, only the value is provided.
z.B
ages :
alice : 25
bob : 24
keys : (( map[ages|k,v|->k] ))
Erträge
ages :
alice : 25
bob : 24
keys :
- alice
- bob
(( map{map|elem|->dynaml-expr} ))
Using {}
instead of []
in the mapping syntax, the result is again a map with the old keys and the new entry values. As for a list mapping additionally a key variable can be specified in the variable list.
persons :
alice : 27
bob : 26
older : (( map{persons|x|->x + 1} ) ))
just increments the value of all entries by one in the field older
:
older :
alice : 28
bob : 27
Bemerkung
An alternate way to express the same is to use sum[persons|{}|s,k,v|->s { k = v + 1 }]
.
(( map{list|elem|->dynaml-expr} ))
Using {}
instead of []
together with a list in the mapping syntax, the result is again a map with the list elements as key and the mapped entry values. For this all list entries must be strings. As for a list mapping additionally an index variable can be specified in the variable list.
persons :
- alice
- bob
length : (( map{persons|x|->length(x)} ) ))
just creates a map mapping the list entries to their length:
length :
alice : 5
bob : 3
(( select[expr|elem|->dynaml-expr] ))
With select
a map or list can be filtered by evaluating a boolean expression for every entry. An entry is selected if the expression evaluates to true equivalent value. (see conditions).
Basically it offers all the mapping flavors available for map[]
z.B
list :
- name : alice
age : 25
- name : bob
age : 26
selected : (( select[list|v|->v.age > 25 ] ))
evaluates selected to
selected :
- name : bob
age : 26
Bemerkung
An alternate way to express the same is to use map[list|v|->v.age > 25 ? v :~]
.
(( select{map|elem|->dynaml-expr} ))
Using {}
instead of []
in the mapping syntax, the result is again a map with the old keys filtered by the given expression.
persons :
alice : 25
bob : 26
older : (( select{persons|x|->x > 25} ))
just keeps all entries with a value greater than 25 and omits all others:
selected :
bob : 26
This flavor only works on maps .
Bemerkung
An alternate way to express the same is to use sum[persons|{}|s,k,v|->v > 25 ? s {k = v} :s]
.
Aggregations are used to produce a single result from the entries of a list or map aggregating the entries by a dynaml expression. The expression is given by a lambda function. There are two basic forms of the aggregation function: It can be inlined as in (( sum[list|0|s,x|->s + x] ))
, or it can be determined by a regular dynaml expression evaluating to a lambda function as in (( sum[list|0|aggregation.expression))
(here the aggregation function is taken from the property aggregation.expression
, which should hold an approriate lambda function).
(( sum[list|initial|sum,elem|->dynaml-expr] ))
Execute an aggregation expression on members of a list to produce an aggregation result. The first expression ( list
) must resolve to a list. The second expression is used as initial value for the aggregation. The last expression ( s + x
) defines the aggregation expression used to aggregate all members of the given list. Inside this expression an arbitrarily declared simple reference name (here s
) can be used to access the intermediate aggregation result and a second reference name (here x
) can be used to access the actually processed list element.
z.B
list :
- 1
- 2
sum : (( sum[list|0|s,x|->s + x] ))
Erträge
list :
- 1
- 2
sum : 3
(( sum[list|initial|sum,idx,elem|->dynaml-expr] ))
In this variant, the second argument idx
is provided with the index and the third elem
with the value for the index.
z.B
list :
- 1
- 2
- 3
prod : (( sum[list|0|s,i,x|->s + i * x ] ))
Erträge
list :
- 1
- 2
- 3
prod : 8
(( sum[map|initial|sum,key,value|->dynaml-expr] ))
Aggregation of the elements of a map to a single result using an aggregation expression. The expression may have access to the key and/or the value. The first argument is always the intermediate aggregation result. If three references are declared, both values are passed to the expression, the second one is provided with the key and the third one with the value for the key. If two references are declared, only the second one is provided with the value of the map entry.
z.B
ages :
alice : 25
bob : 24
sum : (( map[ages|0|s,k,v|->s + v] ))
Erträge
ages :
alice : 25
bob : 24
sum : 49
Projections work over the elements of a list or map yielding a result list. Hereby every element is mapped by an optional subsequent reference expression. This may contain again projections, dynamic references or lambda calls. Basically this is a simplified form of the more general mapping yielding a list working with a lambda function using only a reference expression based on the elements.
(( expr.[*].value ))
All elements of a map or list given by the expression expr
are dereferenced with the subsequent reference expression (here .expr
). If this expression works on a map the elements are ordered accoring to their key values. If the subsequent reference expression is omitted, the complete value list isreturned. For a list expression this means the identity operation.
z.B:
list :
- name : alice
age : 25
- name : bob
age : 26
- name : peter
age : 24
names : (( list.[*].name ))
yields for names
:
names :
- alice
- bob
- peter
or for maps:
networks :
ext :
cidr : 10.8.0.0/16
zone1 :
cidr : 10.9.0.0/16
cidrs : (( .networks.[*].cidr ))
yields for cidrs
:
cidrs :
- 10.8.0.0/16
- 10.9.0.0/16
(( list.[1..2].value ))
This projection flavor only works for lists. The projection is done for a dedicated slice of the initial list.
z.B:
list :
- name : alice
age : 25
- name : bob
age : 26
- name : peter
age : 24
names : (( list.[1..2].name ))
yields for names
:
names :
- bob
- peter
In argument lists or list literals the list expansion operator ( ...
) can be used. It is a postfix operator on any list expression. It substituted the list expression by a sequence of the list members. It can be be used in combination with static list argument denotation.
z.B:
list :
- a
- b
result : (( [ 1, list..., 2, list... ] ))
evaluates result
to
result :
- 1
- a
- b
- 2
- a
- b
The following example demonstrates the usage in combination with the varargs operator in functions:
func : (( |a,b...|-> [a] b ))
list :
- a
- b
a : (( .func(1,2,3) ))
b : (( .func("x",list..., "z") ))
c : (( [ "x", .func(list...)..., "z" ] ))
evaluates the following results:
a :
- 1
- 2
- 3
b :
- x
- a
- b
- z
c :
- x
- a
- b
- z
Please note, that the list expansion might span multiple arguments (including the varargs parameter) in lambda function calls.
Nodes of the yaml document can be marked to enable dedicated behaviours for this node. Such markers are part of the dynaml syntax and may be prepended to any dynaml expression. They are denoted by the &
character directly followed by a marker name. If the expression is a combination of markers and regular expressions, the expression follows the marker list enclosed in brackets (for example (( &temporary( a + b ) ))
).
Note : Instead of using a <<:
insert field to place markers it is possible now to use <<<:
, also, which allows to use regular yaml parsers for spiff-like yaml documents. <<:
is kept for backward compatibility.
(( &temporary ))
Maps, lists or simple value nodes can be marked as temporary . Temporary nodes are removed from the final output document, but are available during merging and dynaml evaluation.
z.B:
temp :
<< : (( &temporary ))
foo : bar
value : (( temp.foo ))
yields:
value : bar
Adding - <<: (( &temporary ))
to a list can be used to mark a list as temporary.
The temporary marker can be combined with regular dynaml expressions to tag plain fields. Hereby the parenthesised expression is just appended to the marker
z.B:
data :
alice : (( &temporary ( "bar" ) ))
foo : (( alice ))
yields:
data :
foo : bar
The temporary marker can be combined with the template marker to omit templates from the final output.
(( &local ))
The marker &local
acts similar to &temporary
but local nodes are always removed from a stub directly after resolving dynaml expressions. Such nodes are therefore not available for merging and they are not used for further merging of stubs and finally the template.
(( &dynamic ))
This marker can be used to mark a template expression (direct or referenced) to enforce the re-evaluation of the template in the usage context whenever the node is used to override or inject a node value along the processing chain. It can also be used together with &inject
or &default
.
z.B:
template.yaml
data : 1
merged with
stub.yaml
id : (( &dynamic &inject &template(__ctx.FILE) ))
will resolve to
id : template.yaml
data : 1
The original template is kept along the merge chain and is evaluated separately in the context of the very stub or template it is used.
Using this marker for nodes not evaluationg to a template value is not possible.
(( &inject ))
This marker requests the marked item to be injected into the next stub level, even is the hosting element (list or map) does not requests a merge. This only works if the next level stub already contains the hosting element.
z.B:
template.yaml
alice :
foo : 1
stub.yaml
alice :
bar : (( &inject(2) ))
nope : not injected
bob :
<< : (( &inject ))
foobar : yep
is merged to
alice :
foo : 1
bar : 2
bob :
foobar : yep
(( &default ))
Nodes marked as default will be used as default values for downstream stub levels. If no such entry is set there it will behave like &inject
and implicitly add this node, but existing settings will not be overwritten.
Maps (or lists) marked as default will be considered as values. The map is used as a whole as default if no such field is defined downstream.
z.B:
template.yaml
data : { }
stub.yaml
data :
foobar :
<< : (( &default ))
foo : claude
bar : peter
is merged to
data :
foobar :
foo : claude
bar : peter
Their entries will neither be used for overwriting existing downstream values nor for defaulting non-existng fields of a not defaulted map field.
z.B:
template.yaml
data :
foobar :
bar : bob
stub.yaml
data :
foobar :
<< : (( &default ))
foo : claude
bar : peter
is merged to
data :
foobar :
bar : bob
If sub sequent defaulting is desired, the fields of a default map must again be marked as default.
z.B:
template.yaml
data :
foobar :
bar : bob
stub.yaml
data :
foobar :
<< : (( &default ))
foo : (( &default ("claude") ))
bar : peter
is merged to
data :
foobar :
foo : claude
bar : bob
Note : The behaviour of list entries marked as default is undefined.
(( &state ))
Nodes marked as state are handled during the merge processing as if the marker would not be present. But there will be a special handling for enabled state processing (option --state <path>
) at the end of the template processing. Additionally to the regular output a document consisting only of state nodes (plus all nested nodes) will be written to a state file. This file will be used as top-level stub for further merge processings with enabled state support.
This enables to keep state between two merge processings. For regular merging sich nodes are only processed during the first processing. Later processings will keep the state from the first one, because those nodes will be overiden by the state stub added to the end of the sub list.
If those nodes additionally disable merging (for example using (( &state(merge none) ))
) dynaml expressions in sub level nodes may perform explicit merging using the function stub()
to refer to values provided by already processed stubs (especially the implicitly added state stub). For an example please refer to the state library.
(( &template ))
Nodes marked as template will not be evaluated at the place of their occurrence. Instead, they will result in a template value stored as value for the node. They can later be instantiated inside a dynaml expression (see below).
(( &tag:name ))
The tag marker can be used to assign a logical name to a node value. This name can then be used in tagged reference expressions to refer to this node value (see below).
A tagged reference has the form <tagname>::<path>
. The <path>
may denote any sub node of a tagged node. If the value of a complete node (or a simple value node) should be used, the <path>
must denote the root path ( .
).
Tags can be used to label node values in multi-document streams (used as template). After defined for a document the tag can then be used to reference node values from the actual or previous document(s) of a document sequence in a multi-document stream. Tags can be added for complex or simple value nodes. A tagged reference may be used to refer to the tagged value as a whole or sub structure.
(( &tag:name(value) ))
This syntax is used to tag a node whose value is defined by a dynaml expression. It can also be used to denote tagged simple value nodes. (As usual the value part is optional for adding markers to structured values (see Markers).)
z.B:
template.yaml
data :
<< : (( &tag:persons ))
alice : (( &tag:alice(25)
If the name is prefixed with a star ( *
), the tag is defined globally. Gobal tags surive stub processing and their value is visible in subsequent stub (and template) processings.
A tag name may consist of multiple components separated by a colon ( :
).
Tags can also be defined dynamically by the dynaml function tagdef.
(( tag::foo ))
Reference a sub path of the value of a tagged node.
z.B:
template.yaml
data :
<< : (( &tag:persons ))
alice : 25
tagref : (( persons::alice ))
resolves tagref
to 25
(( tag::. ))
Reference the whole (structured) value of tagged node.
z.B:
template.yaml
data :
alice : (( &tag:alice(25) ))
tagref : (( alice::. ))
resolves tagref
to 25
(( foo.bar::alice ))
Tag names may be structured. A tag name consists of a non-empty list of tag components separated by a dot or colon ( :
). A tag component may contain ASCII letters or numbers, starting wit a letter. Multi-component tags are subject to Tag Resolution.
A tag reference always contains a tag name and a path separated by a double colon ( ::
). The standard use-case is to describe a dedicated sub node for a tagged node value.
for example, if the tag X
describes the value
data :
alice : 25
bob : 24
the tagged reference X::data.alice
describes the value 25
.
For tagged references with a path other than .
(the whole tag value), structured tags feature a more sophisticated resolution mechanism. A structured tag consist of multiple tag components separated by a colon ( :
), for example lib:mylib
. Therefore, tags span a tree of namespaces or scopes used to resolve path references. A tag-less reference just uses the actual document or binding to resolve a path expression.
Evaluation of a path reference for a tag tries to resolve the path in the first tag tree level where the path is available (breadth-first search). If this level contains multiple tags that could resolve the given path, the resolution fails because it cannot be unambigiously resolved.
Zum Beispiel:
tags :
- << : (( &tag:lib:alice ))
data : alice.alice
- << : (( &tag:lib:alice:v1))
data : alice.v1
- << : (( &tag:lib:bob))
other : bob
usage :
data : (( lib::data ))
effectively resolves usage.data
to lib:alice::data
and therefore to the value alice.alice
.
To achieve this all matching sub tags are orderd by their number of tag components. The first sub-level tag containing such a given path is selected. For this level, the matching tag must be non-ambigious. There must only be one tag with this level containing a matching path. If there are multiple ones the evaluation fails. In the above example this would be the case if tag lib:bob
would contain a field data
instead of or additional to other
.
This feature can be used in library stubs to provide qualified names for their elements that can be used with merging the containing document nodes into the template.
If the template file is a multi-document stream the tags are preserved during the complete processing. This means tags defined in a earlier document can be used in all following documents, also. But the tag names must be unique across all documents in a multi-document stream.
z.B:
template.yaml
<< : (( &temporary ))
data :
<< : (( &tag:persons ))
alice : 25
bob : 24
---
alice : (( persons::alice ))
---
bob : (( persons::bob ))
resolves to
---
alice : 25
---
bob : 24
Tags defined by tag markers are available for stubs and templates. Global tags are available down the stub processing to the templates. Local tags are only avaialble on the processing level they are declared.
Additionally to the tags explicitly set by tag markers, there are implicit document tags given by the document index during the processing of a (multi-document) template. The implicit document tags are qualified with the prefix doc.
. This prefix should not be used to own tags in the documents
z.B:
template.yaml
<< : (( &temporary ))
data :
<< : (( &tag:persons ))
alice : 25
bob : 24
---
alice : (( persons::alice ))
prev : (( doc.1::. ))
---
bob : (( persons::bob ))
prev : (( doc.2::. ))
resolves to
---
alice : 25
prev :
data :
alice : 25
bob : 24
---
bob : 24
prev :
alice : 25
prev :
data :
alice : 25
bob : 24
If the given document index is negative it denotes the document relative to the one actually processed (so, the tag doc.-1
denotes the previous document). The index doc.0
can be used to denote the actual document. Here always a path must be specified, it is not possible to refer to the complete document (with .
).
A map can be tagged by a dynaml expression to be used as template. Dynaml expressions in a template are not evaluated at its definition location in the document, but can be inserted at other locations using dynaml. At every usage location it is evaluated separately.
<<: (( &template ))
The dynaml expression &template
can be used to tag a map node as template:
z.B:
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
The template will be the value of the node foo.bar
. As such it can be overwritten as a whole by settings in a stub during the merge process. Dynaml expressions in the template are not evaluated. A map can have only a single <<
field. Therefore it is possible to combine the template marker with an expression just by adding the expression in parenthesis.
Adding - <<: (( &template ))
to a list it is also possible to define list templates. It is also possible to convert a single expression value into a simple template by adding the template marker to the expression, for example foo: (( &template (expression) ))
The template marker can be combined with the temporary marker to omit templates from the final output.
Note : Instead of using a <<:
insert field to place the template marker it is possible now to use <<<:
, also, which allows to use regular yaml parsers for spiff-like yaml documents. <<:
is kept for backward compatibility.
(( *foo.bar ))
The dynaml expression *<reference expression>
can be used to evaluate a template somewhere in the yaml document. Dynaml expressions in the template are evaluated in the context of this expression.
z.B:
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
use :
subst : (( *foo.bar ))
verb : loves
verb : hates
evaluates to
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
use :
subst :
alice : alice
bob : loves alice
verb : loves
verb : hates
_
The special reference _
( self ) can be used inside of lambda functions and templates . They refer to the containing element (the lambda function or template).
Additionally it can be used to lookup relative reference expressions starting with the defining document scope of the element skipping intermediate scopes.
z.B:
node :
data :
scope : data
funcs :
a : (( |x|->scope ))
b : (( |x|->_.scope ))
c : (( |x|->_.data.scope ))
scope : funcs
call :
scope : call
a : (( node.funcs.a(1) ))
b : (( node.funcs.b(1) ))
c : (( node.funcs.c(1) ))
evaluates call
to
call :
a : call
b : funcs
c : data
scope : call
__
The special reference __
can be used to lookup references as relative references starting with the document node hosting the actually evaluated dynaml expression skipping intermediate scopes.
This can, for example be used to relatively access a lambda value field besides the actual field in a map. The usage of plain function names is reserved for builtin functions and are not used as relative references.
This special reference is also available in expressions in templates and refer to the map node in the template hosting the actually evaluated expression.
z.B:
templates :
templ :
<< : (( &template ))
self : (( _ ))
value : (( ($self="value") __.self ))
result : (( scope ))
templ : (( _.scope ))
scope : templates
result :
inst : (( *templates.templ ))
scope : result
evaluates result
to
result :
inst :
result : result
templ : templates
self :
<< : (( &template ))
result : (( scope ))
self : (( _ ))
templ : (( _.scope ))
value : (( ($self="value") __.self ))
value :
<< : (( &template ))
result : (( scope ))
self : (( _ ))
templ : (( _.scope ))
value : (( ($self="value") __.self ))
scope : result
or with referencing upper nodes:
templates :
templ :
<< : (( &template ))
alice : root
data :
foo : (( ($bob="local") __.bob ))
bar : (( ($alice="local") __.alice ))
bob : static
result : (( *templates.templ ))
evaluates result
to
result :
alice : root
data :
bar : root
foo : static
bob : static
___
The special reference ___
can be used to lookup references in the outer most scope. It can therefore be used to access processing bindings specified for a document processing via command line or API. If no bindings are specified the document root is used.
Calling spiff merge template.yaml --bindings bindings.yaml
with a binding of
bindings.yaml
input1 : binding1
input2 : binding2
and the template
template.yaml
input1 : top1
map :
input : map
input1 : map1
results :
frommap : (( input1 ))
fromroot : (( .input1 ))
frombinding1 : (( ___.input1 ))
frombinding2 : (( input2 ))
evaluates map.results
to
results :
frombinding1 : binding1
frombinding2 : binding2
frommap : map1
fromroot : top1
__ctx.OUTER
The context field OUTER
is used for nested merges. It is a list of documents, index 0 is the next outer document, and so on.
(( {} ))
Provides an empty map.
(( [] ))
Provides an empty list. Basically this is not a dedicated literal, but just a regular list expression without a value.
(( ~ ))
Provides the null value.
(( ~~ ))
This literal evaluates to an undefined expression. The element (list entry or map field) carrying this value, although defined, will be removed from the document and handled as undefined for further merges and the evaluation of referential expressions.
z.B:
foo : (( ~~ ))
bob : (( foo || ~~ ))
alice : (( bob || "default"))
evaluates to
alice : default
Inside every dynaml expression a virtual field __ctx
is available. It allows access to information about the actual evaluation context. It can be accessed by a relative reference expression.
The following fields are supported:
Field Name | Typ | Bedeutung |
---|---|---|
VERSION | Zeichenfolge | current version of spiff |
FILE | Zeichenfolge | name of actually processed template file |
DIR | Zeichenfolge | name of directory of actually processed template file |
RESOLVED_FILE | Zeichenfolge | name of actually processed template file with resolved symbolic links |
RESOLVED_DIR | Zeichenfolge | name of directory of actually processed template file with resolved symbolic links |
PATHNAME | Zeichenfolge | path name of actually processed field |
PATH | list[string] | path name as component list |
OUTER | yaml doc | outer documents for nested merges, index 0 is the next outer document |
BINDINGS | yaml doc | the external bindings for the actual processing (see also ___) |
If external bindings are specified they are the last elements in OUTER
.
z.B:
template.yml
foo :
bar :
path : (( __ctx.PATH ))
str : (( __ctx.PATHNAME ))
file : (( __ctx.FILE ))
dir : (( __ctx.DIR ))
evaluates to
z.B:
foo :
bar :
dir : .
file : template.yml
path :
- foo
- bar
- path
str : foo.bar.str
Dynaml expressions are evaluated obeying certain priority levels. This means operations with a higher priority are evaluated first. For example the expression 1 + 2 * 3
is evaluated in the order 1 + ( 2 * 3 )
. Operations with the same priority are evaluated from left to right (in contrast to version 1.0.7). This means the expression 6 - 3 - 2
is evaluated as ( 6 - 3 ) - 2
.
The following levels are supported (from low priority to high priority)
||
, //
foo bar
)-or
, -and
==
, !=
, <=
, <
, >
, >=
+
, -
*
, /
, %
( )
, !
, constants, references ( foo.bar
), merge
, auto
, lambda
, map[]
, and functionsThe complete grammar can be found in dynaml.peg.
Feature state: alpha
Attention: This is an alpha feature. It must be enabled on the command line with the --interpolation
or --features=interpolation
option. Also for the spiff library it must explicitly be enabled. By adding the key interpolation
to the feature list stored in the environment variable SPIFF_FEATURES
this feature will be enabled by default.
Typically a complete value can either be a literal or a dynaml expression. For string literals it is possible to use an interpolation syntax to embed dynaml expressions into strings.
Zum Beispiel
data : test
interpolation : this is a (( data ))
replaces the part between the double brackets by the result of the described expression evaluation. Here the brackets can be escaped by the usual escaping ( ((!
) syntax.
Those string literals will implicitly be converted to complete flat dynaml expressions. The example above will therefore be converted into
(( "this is a " data ))
which is the regular dynaml equivalent. The escaping is very ticky, and may be there are still problems. Quotes inside an embedded dynaml expression can be escaped to enable quotes in string literals.
Incomplete or partial interpolation expressions will be ignored and just used as string.
Strings inside a dynaml expression are NOT directly interpolated again, thus
data : " test "
interpolated : " this is a (( length( " (( data )) " ) data )) "
will resolve interpolation
to this is 10test
and not to this is 4test
.
But if the final string after the expression evaluation again describes a string interpolation it will be processed, again.
data : test
interpolation : this is a (( "(( data ))" data ))
will resolve interpolation
to this is testtest
.
The embedded dynaml expression must be concatenatable with strings.
Feature state: alpha
In addition to describe conditions and loops with dynaml expressions it is also possible to use elements of the document structure to embed control structures.
Such a YAML-based control structure is always described as a map in YAML/JSON. The syntactical elements are expressed as map fields starting with <<
. Additionally, depending on the control structure, regular fields are possible. Control structures finally represent a value for the containing node. They may contain marker expressions ( <<
), also.
z.B:
temp :
<< : (( &temporary ))
<<if : (( features("control") ))
<<then :
alice : 25
bob : 26
resolves to
final :
alice : 25
bob : 26
Tu use this alpha feature the feature flag control
must be enabled.
Please be aware: Control structure maps typically are always completely resolved before they are evaluated.
A control structure itself is always a dedicated map node in a document. It is substituted by a regular value node determined by the execution of the control structure.
The fields of a control structure map are not subject to overwriting by stubs, but the complete structure can be overwritten.
If used as value for a map field the resulting value is just used as effective value for this field.
If a map should be enriched by maps resulting from multiple control structures the special control structure <<merge:
can be used. It allows to specify a list of maps which should be merged with the actual control structure map to finally build the result value.
A control structure can be used as list value, also. In this case there is a dedicated interpretation of the resulting value of the control structure. If it is NOT a list value, for convenience, the value is directly used as list entry and substitutes the control structure map.
If the resulting value is again a list, it is inserted into the containing list at the place of occurrence of the control structure. So, if a list value should be used as dedicated entry in a list, the result of a control structure must be a list with the intended list as entry.
z.B:
list :
- <<if : (( features("control") ))
<<then : alice
- <<if : (( features("control") ))
<<then :
- - peter
- <<if : (( features("control") ))
<<then :
- bob
resolves to
list :
- alice
- - peter
- bob
<<if:
The condition structure is defined by the syntax field <<if
. It additionally accepts the fields <<then
and <<else
.
The condition field must provide a boolean value. If it is true
the optional <<then
field is used to substitute the control structure, otherwise the optional <<else
field is used.
If the appropriate case is not specified, the result is the undefined (( ~~ ))
value. The containing field is therefore completely omitted from the output.
.z.B:
x : test1
cond :
field :
<<if : (( x == "test" ))
<<then : alice
<<else : bob
evaluates cond.field
to bob
If the else case is omitted, the cond
field would be an empty map ( field
is omitted, because the contained control structure evaluates to undefined )
A comparable way to do this with regular dynaml could look like this:
cond : (( x == "test" ? "alice" :"bob" ))
A better way more suitable for complex cases would be:
local :
<< : (( &local))
then : alice
else : bob
cond : (( x == "test" ? local.then :local.else ))
<<switch:
The switch
control structure evaluates the switch value of the <<switch
field to a string and uses it to select an appropriate regular field in the control map.
If it is not found the value of the optional field <<default
is used. If no default is specified, the control structure evaluates to an error, if no appropriate regular field is available.
The nil value matches the default
case. If the switch value is undefined the control evaluates to the undefined value (( ~~ ))
.
z.B:
x : alice
value :
<<switch : (( x ))
alice : 25
bob : 26
<<default : other
evaluates value
to 25
.
A comparable way to do this with regular dynaml could look like this:
local :
<< : (( &local))
cases :
alice : 25
bob : 26
default : other
value : (( local.cases[x] || local.default ))
<<type:
The type
control structure evaluates the type of the value of the <<type
field and uses it to select an appropriate regular field in the control map.
If it is not found the value of the optional field <<default
is used. If no default is specified, the control structure evaluates to an error, if no appropriate regular field is available.
z.B:
x : alice
value :
<<type : (( x ))
string : alice
<<default : unknown
evaluates value
to alice
.
A comparable way to do this with regular dynaml could look like this:
local :
<< : (( &local))
cases :
string : alice
default : unknown
value : (( local.cases[type(x)] || local.default ))
For more complex scenarios not only switching on strings a second syntax can be used. Instead of using fields in the control map as cases, a dedicated field <<cases
may contain a list of cases, that are checked sequentially (In this flavor regular fields are not allowed anymore).
Every case is described again by a map containing the fields:
case
: the expected value to match the switch valuematch
: a lambda function taking one argument and yielding a boolean value used to match the given switch valuevalue
: (optional) the resulting value in case of a match. If not defined the result will be the undefined value. One of case
or match
must be present.
z.B:
x : 5
selected :
<<switch : (( x ))
<<cases :
- case :
alice : 25
value : alice
- match : (( |v|->v == 5 ))
value : bob
<<default : unknown
resolves to
x : 5
selected : bob
If x
would be set to the complex value
x :
alice : 25
it would resolve to
x :
alice : 25
selected : alice
<<for:
The loop control is able to execute a multi-dimensional loop and produce a list or map based on the value combinations.
The loop ranges are specified by the value of the <<for
field. It is possible ol loop over lists or maps. The range specification can be given by either a map or list:
map : the keys of the map are the names of the control variables and the values must be lists or maps specifying the ranges.
The map key might optionally be a comma-separated pair (for example key,value
) of variable names. In this case the first name is the name for the index variable and the second one for the value variable.
If multiple ranges are specified iterations are alphabetically ordered by value variable name (first) and index variable name (second) to determine the traversing order.
list : if the control variables are defined by a list, each list element must contain two mandatory and one optional field(s):
name
: the name of the (list or map entry value) control variablevalues
: a list to define the value range.index
: (optional) the name of the variable providing the list index or map key of the loop range (defaulted to index-<name>
)Here the order in the list determine the traversal order.
Traversal is done by recursively iterating follow up ranges for every entry in the actual range. This means the last range is completely iterated for the first values of the first ranges first.
If no index variable is specified for a loop range there is an additional implicit binding for every control variable describing the actual list index or map key of the processed value for this dimension. It is denoted by index-<control variable>
If multiple loop ranges are specified, the ranges may mix iterations over maps and lists.
The iteration result value is determined by the value of the <<do
field. It is implicitly handled as template and is evaluated for every set of iteration values.
The result of the evaluation using only the <<do
value field is a list.
z.B:
alice :
- a
- b
bob :
- 1
- 2
- 3
list :
<<for :
key,alice : (( .alice )) # sorted by using alice as primary sort key
bob : (( .bob ))
<<do :
value : (( alice "-" key "-" bob "-" index-bob ))
evaluates list
to
list :
- value : a-0-1-0
- value : a-0-2-1
- value : a-0-3-2
- value : b-1-1-0
- value : b-1-2-1
- value : b-1-3-2
It first iterates over the values for alice
. For each such value it then iterates over the values of bob
.
A comparable way to do this with regular dynaml could look like this:
list : (( sum[alice|[]|s,key,alice|-> s sum[bob|[]|s,index_bob,bob|->s (alice "-" key "-" bob "-" index_bob)]] ))
A result list may omit entries if the value expression evaluates to the undefined value ( ~~
). The nil value ( ~
) is kept. This way a for
control can be used to filter lists.
z.B:
bob :
- 1
- 2
- 3
filtered :
<<for :
bob : (( .bob ))
<<do : (( bob == 2 ? ~~ :bob ))
resolves to
bob :
- 1
- 2
- 3
filtered :
- 1
- 3
If the result should be a map it is required to additionally specify a key value for every iteration. This is specified by the optional <<mapkey
field. Like the <<do
field it is implicitly handled as template and re-evaluated for every iteration.
z.B:
x : suffix
alice :
- a
- b
bob :
- 1
- 2
- 3
map :
<<for :
- name : alice
values : (( .alice ))
- name : bob
values : (( .bob ))
<<mapkey : (( alice bob ))
<<do :
value : (( alice bob x ))
evaluates the field map
to
map :
a1 :
value : a1suffix
a2 :
value : a2suffix
a3 :
value : a3suffix
b1 :
value : b1suffix
b2 :
value : b2suffix
b3 :
value : b3suffix
Here the traversal order is irrelevant as long as the generated key values are unique. If several evaluations of the key expression yield the same value the last one will win.
A comparable way to do this with regular dynaml could look like this:
map : (( sum[alice|{}|s,index_alice,alice|-> s sum[bob|{}|s,index_bob,bob|->s {(alice bob)=alice bob x}]] ))
An iteration value is ignored if the key or the value evaluate to the undefined value (( ~~ ))
. Additionally the key may evaluate to the nil value (( ~ ))
, also.
z.B:
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
<<for :
key,bob : (( .bob ))
<<mapkey : (( key ))
<<do : (( bob == 2 ? ~~ :bob ))
oder
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
<<for :
key,bob : (( .bob ))
<<mapkey : (( bob == 2 ? ~~ :key ))
<<do : (( bob ))
resolve to
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
b1 : 1
b3 : 3
<<merge:
With merge
it is possible to merge maps given as list value of the <<merge
field with regular map fields from the control structure to determine the final map value.
The value for <<merge:
may be a single map or a list of maps to join with the directly given fields.
z.B:
map :
<<merge :
- bob : 26
charlie : 1
- charlie : 27
alice : 25
charlie : 2
resolves to
map :
alice : 25
bob : 26
charlie : 27
If multiple maps contain the same key, the last value (in order of list) will win.
This might be combined with other control structures, for example to conditionally merge multiple maps:
z.B:
x : charlie
map :
<<merge :
- <<if : (( x == "charlie" ))
<<then :
charlie : 27
- <<if : (( x == "alice" ))
<<then :
alice : 20
alice : 25
charlie : 2
resolves to
x : charlie
map :
alice : 25
charlie : 27
By default spiff
performs a deep structural merge of its first argument, the template file, with the given stub files. The merge is processed from right to left, providing an intermediate merged stub for every step. This means, that for every step all expressions must be locally resolvable.
Structural merge means, that besides explicit dynaml merge
expressions, values will be overridden by values of equivalent nodes found in right-most stub files. In general, flat value lists are not merged. Only lists of maps can be merged by entries in a stub with a matching index.
There is a special support for the auto-merge of lists containing maps, if the maps contain a name
field. Hereby the list is handled like a map with entries according to the value of the list entries' name
field. If another key field than name
should be used, the key field of one list entry can be tagged with the prefix key:
to indicate the indended key name. Such tags will be removed for the processed output.
In general the resolution of matching nodes in stubs is done using the same rules that apply for the reference expressions (( foo.bar.[1].baz )).
For example, given the file template.yml :
foo :
- name : alice
bar : template
- name : bob
bar : template
plip :
- id : 1
plop : template
- id : 2
plop : template
bar :
- foo : template
list :
- a
- b
and file stub.yml :
foo :
- name : bob
bar : stub
plip :
- key:id : 1
plop : stub
bar :
- foo : stub
list :
- c
- d
spiff merge template.yml stub.yml
kehrt zurück
foo :
- bar : template
name : alice
- bar : stub
name : bob
plip :
- id : 1
plop : stub
- id : 2
plop : template
bar :
- foo : stub
list :
- a
- b
Be careful that any name:
key in the template for the first element of the plip
list will defeat the key:id: 1
selector from the stub. When a name
field exist in a list element, then this element can only be targeted by this name. When the selector is defeated, the resulting value is the one provided by the template.
Merging the following files in the given order
deployment.yml
networks : (( merge ))
cf.yml
utils : (( merge ))
network : (( merge ))
meta : (( merge ))
networks :
- name : cf1
<< : (( utils.defNet(network.base.z1,meta.deployment_no,30) ))
- name : cf2
<< : (( utils.defNet(network.base.z2,meta.deployment_no,30) ))
infrastructure.yml
network :
size : 16
block_size : 256
base :
z1 : 10.0.0.0
z2 : 10.1.0.0
rules.yml
utils :
defNet : (( |b,n,s|->(*.utils.network).net ))
network :
<< : (( &template ))
start : (( b + n * .network.block_size ))
first : (( start + ( n == 0 ? 2 :0 ) ))
lower : (( n == 0 ? [] :b " - " start - 1 ))
upper : (( start + .network.block_size " - " max_ip(net.subnets.[0].range) ))
net :
subnets :
- range : (( b "/" .network.size ))
reserved : (( [] lower upper ))
static :
- (( first " - " first + s - 1 ))
instance.yml
meta :
deployment_no : 1
will yield a network setting for a dedicated deployment
networks :
- name : cf1
subnets :
- range : 10.0.0.0/16
reserved :
- 10.0.0.0 - 10.0.0.255
- 10.0.2.0 - 10.0.255.255
static :
- 10.0.1.0 - 10.0.1.29
- name : cf2
subnets :
- range : 10.1.0.0/16
reserved :
- 10.1.0.0 - 10.1.0.255
- 10.1.2.0 - 10.1.255.255
static :
- 10.1.1.0 - 10.1.1.29
Using the same config for another deployment of the same type just requires the replacement of the instance.yml
. Using a different instance.yml
meta :
deployment_no : 0
will yield a network setting for a second deployment providing the appropriate settings for a unique other IP block.
networks :
- name : cf1
subnets :
- range : 10.0.0.0/16
reserved :
- 10.0.1.0 - 10.0.255.255
static :
- 10.0.0.2 - 10.0.0.31
- name : cf2
subnets :
- range : 10.1.0.0/16
reserved :
- 10.1.1.0 - 10.1.255.255
static :
- 10.1.0.2 - 10.1.0.31
If you move to another infrastructure you might want to change the basic IP layout. You can do it just by adapting the infrastructure.yml
network :
size : 17
block_size : 128
base :
z1 : 10.0.0.0
z2 : 10.0.128.0
Without any change to your other settings you'll get
networks :
- name : cf1
subnets :
- range : 10.0.0.0/17
reserved :
- 10.0.0.128 - 10.0.127.255
static :
- 10.0.0.2 - 10.0.0.31
- name : cf2
subnets :
- range : 10.0.128.0/17
reserved :
- 10.0.128.128 - 10.0.255.255
static :
- 10.0.128.2 - 10.0.128.31
There are several scenarios yielding results that do not seem to be obvious. Here are some typical pitfalls.
The auto merge never adds nodes to existing structures
For example, merging
template.yml
foo :
alice : 25
mit
stub.yml
foo :
alice : 24
bob : 26
Erträge
foo :
alice : 24
Use <<: (( merge )) to change this behaviour, or explicitly add desired nodes to be merged:
template.yml
foo :
alice : 25
bob : (( merge ))
Simple node values are replaced by values or complete structures coming from stubs, structures are deep merged.
For example, merging
template.yml
foo : (( ["alice"] ))
mit
stub.yml
foo :
- peter
- paul
Erträge
foo :
- peter
- paul
But the template
foo : [ (( "alice" )) ]
is merged without any change.
Expressions are subject to be overridden as a whole
A consequence of the behaviour described above is that nodes described by an expession are basically overridden by a complete merged structure, instead of doing a deep merge with the structues resulting from the expression evaluation.
For example, merging
template.yml
men :
- bob : 24
women :
- alice : 25
people : (( women men ))
mit
stub.yml
people :
- alice : 13
Erträge
men :
- bob : 24
women :
- alice : 25
people :
- alice : 24
To request an auto-merge of the structure resulting from the expression evaluation, the expression has to be preceeded with the modifier prefer
( (( prefer women men ))
). This would yield the desired result:
men :
- bob : 24
women :
- alice : 25
people :
- alice : 24
- bob : 24
Nested merge expressions use implied redirections
merge
expressions implicity use a redirection implied by an outer redirecting merge. In the following example
meta :
<< : (( merge deployments.cf ))
properties :
<< : (( merge ))
alice : 42
the merge expression in meta.properties
is implicity redirected to the path deployments.cf.properties
implied by the outer redirecting merge
. Therefore merging with
deployments :
cf :
properties :
alice : 24
bob : 42
Erträge
meta :
properties :
alice : 24
bob : 42
Functions and mappings can freely be nested
z.B:
pot : (( lambda |x,y|-> y == 0 ? 1 :(|m|->m * m)(_(x, y / 2)) * ( 1 + ( y % 2 ) * ( x - 1 ) ) ))
seq : (( lambda |b,l|->map[l|x|-> .pot(b,x)] ))
values : (( .seq(2,[ 0..4 ]) ))
yields the list [ 1,2,4,8,16 ]
for the property values
.
Functions can be used to parameterize templates
The combination of functions with templates can be use to provide functions yielding complex structures. The parameters of a function are part of the scope used to resolve reference expressions in a template used in the function body.
z.B:
relation :
template :
<< : (( &template ))
bob : (( x " " y ))
relate : (( |x,y|->*relation.template ))
banda : (( relation.relate("loves","alice") ))
evaluates to
relation :
relate : lambda|x,y|->*(relation.template)
template :
<< : (( &template ))
bob : (( x " " y ))
banda :
bob : loves alice
Scopes can be used to parameterize templates
Scope literals are also considered when instantiating templates. Therefore they can be used to set explicit values for relative reference expressions used in templates.
z.B:
alice : 1
template :
<< : (( &template ))
sum : (( alice + bob ))
scoped : (( ( $alice = 25, "bob" = 26 ) *template ))
evaluates to
alice : 1
template :
<< : (( &template ))
sum : (( alice + bob ))
scoped :
sum : 51
Aggregations may yield complex values by using templates
The expression of an aggregation may return complex values by returning inline lists or instantiated templates. The binding of the function will be available (as usual) for the evaluation of the template. In the example below the aggregation provides a map with both the sum and the product of the list entries containing the integers from 1 to 4.
z.B:
sum : (( sum[[1..4]|init|s,e|->*temp] ))
temp :
<< : (( &template ))
sum : (( s.sum + e ))
prd : (( s.prd * e ))
init :
sum : 0
prd : 1
yields for sum
the value
sum:
prd: 24
sum: 10
Taking advantage of the undefined value
At first glance it might look strange to introduce a value for undefined . But it can be really useful as will become apparent with the following examples.
Whenever a stub syntactically defines a field it overwrites the default in the template during merging. Therefore it would not be possible to define some expression for that field that eventually keeps the default value. Here the undefined value can help:
eg: merging
template.yml
alice : 24
bob : 25
mit
stub.yml
alice : (( config.alice * 2 || ~ ))
bob : (( config.bob * 3 || ~~ ))
Erträge
alice : ~
bob : 25
There is a problem accessing upstream values. This is only possible if the local stub contains the definition of the field to use. But then there will always be a value for this field, even if the upstream does not overwrite it.
Here the undefined value can help by providing optional access to upstream values. Optional means, that the field is only defined, if there is an upstream value. Otherwise it is undefined for the expressions in the local stub and potential downstream templates. This is possible because the field is formally defined, and will therefore be merged, only after evaluating the expression if it is not merged it will be removed again.
eg: merging
template.yml
alice : 24
bob : 25
peter : 26
mit
mapping.yml
config :
alice : (( ~~ ))
bob : (( ~~ ))
alice : (( config.alice || ~~ ))
bob : (( config.bob || ~~ ))
peter : (( config.peter || ~~ ))
Und
config.yml
config :
alice : 4711
peter : 0815
Erträge
alice : 4711 # transferred from config's config value
bob : 25 # kept default value, because not set in config.yml
peter : 26 # kept, because mapping source not available in mapping.yml
This can be used to add an intermediate stub, that offers a dedicated configuration interface and contains logic to map this interface to a manifest structure already defining default values.
Templates versus map literals
As described earlier templates can be used inside functions and mappings to easily describe complex data structures based on expressions refering to parameters. Before the introduction of map literals this was the only way to achieve such behaviour. The advantage is the possibility to describe the complex structure as regular part of a yaml document, which allows using the regular yaml formatting facilitating readability.
z.B:
scaling :
runner_z1 : 10
router_z1 : 4
jobs : (( sum[scaling|[]|s,k,v|->s [ *templates.job ] ] ))
templates :
job :
<< : (( &template ))
name : (( k ))
instances : (( v ))
evaluates to
scaling :
runner_z1 : 10
router_z1 : 4
jobs :
- instances : 4
name : router_z1
- instances : 10
name : runner_z1
...
With map literals this construct can significantly be simplified
scaling :
runner_z1 : 10
router_z1 : 4
jobs : (( sum[scaling|[]|s,k,v|->s [ {"name"=k, "value"=v} ] ] ))
Nevertheless the first, template based version might still be useful, if the data structures are more complex, deeper or with complex value expressions. For such a scenario the description of the data structure as template should be preferred. It provides a much better readability, because every field, list entry and value expression can be put into dedicated lines.
But there is still a qualitative difference. While map literals are part of a single expression always evaluated as a whole before map fields are available for referencing, templates are evaluated as regular yaml documents that might contain multiple fields with separate expressions referencing each other.
z.B:
range : (( (|cidr,first,size|->(*templates.addr).range)("10.0.0.0/16",10,255) ))
templates :
addr :
<< : (( &template ))
base : (( min_ip(cidr) ))
start : (( base + first ))
end : (( start + size - 1 ))
range : (( start " - " end ))
evaluates range
to
range : 10.0.0.10 - 10.0.1.8
...
Defaulting and Requiring Fields
Traditionally defaulting in spiff is done by a downstream template where the playload data file is used as stub.
Fields with simple values can just be specified with their values. They will be overwritten by stubs using the regular spiff document merging mechanisms.
It is more difficult for maps or lists. If a map is specified in the template only its fields will be merged (see above), but it is never replaced as a whole by settings in the playload definition files. And Lists are never merged.
Therefore maps and lists that should be defaulted as a whole must be specified as initial expressions (referential or inline) in the template file.
eg: merging of
template.yaml
defaults :
<< : (( &temporary ))
person :
name : alice
age : bob
config :
value1 : defaultvalue
value2 : defaultvalue
person : (( defaults.person ))
Und
payload.yaml
config :
value2 : configured
othervalue : I want this but don't get it
evaluates to
config :
person :
age : bob
name : alice
value1 : defaultvalue
value2 : configured
In such a scenario the structure of the resulting document is defined by the template. All kinds of variable fields or sub-structures must be forseen by the template by using <<: (( merge ))
expressions in maps.
eg: changing template to
template.yaml
defaults :
<< : (( &temporary ))
person :
name : alice
age : bob
config :
<< : (( merge ))
value1 : defaultvalue
value2 : defaultvalue
person : (( defaults.person ))
Known optional fields can be described using the undefined ( ~~
) expression:
template.yaml
config :
optional : (( ~~ ))
Such fields will only be part of the final document if they are defined in an upstream stub, otherwise they will be completely removed.
Required fields can be defined with the expression (( merge ))
. If no stub contains a value for this field, the merge cannot be fullfilled and an error is reported. If a dedicated message should be shown instead, the merge expression can be defaulted with an error function call.
z.B:
template.yaml
config :
password : (( merge || error("the field password is required") ))
will produce the following error if no stub contains a value:
error generating manifest: unresolved nodes:
(( merge || error("the field password is required") )) in c.yaml config.password () *the field password is required
This can be simplified by reducing the expression to the sole error
expression.
Besides this template based defaulting it is also possible to provide defaults by upstream stubs using the &default
marker. Here the payload can be a downstream file.
X509 and providing State
When generating keys or certificates with the X509 Functions there will be new keys or certificates for every execution of spiff . But it is also possible to use spiff to maintain key state. A very simple script could look like this:
#! /bin/bash
DIR= " $( dirname " $0 " ) /state "
if [ ! -f " $DIR /state.yaml " ] ; then
echo " state: " > " $DIR /state.yaml "
fi
spiff merge " $DIR /template.yaml " " $DIR /state.yaml " > " $DIR /. $$ " && mv " $DIR /. $$ " " $DIR /state.yaml "
It uses a template file (containing the rules) and a state file with the actual state as stub. The first time it is executed there is an empty state and the rules are not overridden, therefore the keys and certificates are generated. Later on, only additional new fields are calculated, the state fields already containing values just overrule the dynaml expressions for those fields in the template.
If a re-generation is required, the state file can just be deleted.
A template may look like this:
state/template.yaml
spec :
<< : (( &local ))
ca :
organization : Mandelsoft
commonName : rootca
privateKey : (( state.cakey ))
isCA : true
usage :
- Signature
- KeyEncipherment
peer :
organization : Mandelsoft
commonName : etcd
publicKey : (( state.pub ))
caCert : (( state.cacert ))
caPrivateKey : (( state.cakey ))
validity : 100
usage :
- ServerAuth
- ClientAuth
- KeyEncipherment
hosts :
- etcd.mandelsoft.org
state :
cakey : (( x509genkey(2048) ))
capub : (( x509publickey(cakey) ))
cacert : (( x509cert(spec.ca) ))
key : (( x509genkey(2048) ))
pub : (( x509publickey(key) ))
peer : (( x509cert(spec.peer) ))
The merge then generates a rootca and some TLS certificate signed with this CA.
Generating, Deploying and Accessing Status for Kubernetes Resources
The sync
function offers the possibility to synchronize the template processing with external content. This can also be the output of a command execution. Therefore the template processing can not only be used to generate a deployment manifest, but also for applying this to a target system and retrieving deployment status values for the further processing.
A typical scenario of this kind could be a kubernetes setup including a service of type LoadBalancer . Once deployed it gets assigned status information about the IP address or hostname of the assigned load balancer. This information might be required for some other deployment manifest.
A simple template for such a deployment could like this:
service :
apiVersion : v1
kind : Service
metadata :
annotations :
dns.mandelsoft.org/dnsnames : echo.test.garden.mandelsoft.org
dns.mandelsoft.org/ttl : " 500 "
name : test-service
namespace : default
spec :
ports :
- name : http
port : 80
protocol : TCP
targetPort : 8080
sessionAffinity : None
type : LoadBalancer
deployment :
testservice : (( sync[pipe_uncached(service, "kubectl", "apply", "-f", "-", "-o", "yaml")|value|->defined(value.status.loadBalancer.ingress)] ))
otherconfig :
lb : (( deployment.testservice.status.loadBalancer.ingress ))
Crazy Shit: Graph Analaysis with spiff
It is easy to describe a simple graph with knots and edges (for example for a set of components and their dependencies) just by using a map of lists.
graph :
a :
- b
- c
b : []
c :
- b
- a
d :
- b
e :
- d
- b
Now it would be useful to figure out whether there are dependency cycles or to determine ordered transitive dependencies for a component.
Let's say something like this:
graph :
utilities :
closures : (( utilities.graph.evaluate(graph) ))
cycles : (( utilities.graph.cycles(closures) ))
Indeed, this can be done with spiff. The only thing required is a "small utilities stub" .
utilities :
<< : (( &temporary ))
graph :
_dep : (( |model,comp,closure|->contains(closure,comp) ? { $deps=[], $err=closure [comp]} :($deps=_._deps(model,comp,closure [comp]))($err=sum[deps|[]|s,e|-> length(s) >= length(e.err) ? s :e.err]) { $deps=_.join(map[deps|e|->e.deps]), $err=err} ))
_deps : (( |model,comp,closure|->map[model.[comp]|dep|->($deps=_._dep(model,dep,closure)) { $deps=[dep] deps.deps, $err=deps.err }] ))
join : (( |lists|->sum[lists|[]|s,e|-> s e] ))
min : (( |list|->sum[list|~|s,e|-> s ? e < s ? e :s :e] ))
normcycle : (( |cycle|->($min=_.min(cycle)) min ? sum[cycle|cycle|s,e|->s.[0] == min ? s :(s.[1..] [s.[1]])] :cycle ))
cycle : (( |list|->list ? ($elem=list.[length(list) - 1]) _.normcycle(sum[list|[]|s,e|->s ? s [e] :e == elem ? [e] :s]) :list ))
norm : (( |deps|->{ $deps=_.reverse(uniq(_.reverse(deps.deps))), $err=_.cycle(deps.err) } ))
reverse : (( |list|->sum[list|[]|s,e|->[e] s] ))
evaluate : (( |model|->sum[model|{}|s,k,v|->s { k=_.norm(_._dep(model,k,[]))}] ))
cycles : (( |result|->uniq(sum[result|[]|s,k,v|-> v.err ? s [v.err] :s]) ))
And magically spiff does the work just by calling
spiff merge closure.yaml graph.yaml utilities.yaml
closures :
a :
deps :
- c
- b
- a
err :
- a
- c
- a
b :
deps : []
err : []
c :
deps :
- a
- b
- c
err :
- a
- c
- a
d :
deps :
- b
err : []
e :
deps :
- d
- b
err : []
cycles :
- - a
- c
- a
graph :
a :
- b
- c
b : []
c :
- b
- a
d :
- b
e :
- d
- b
The evaluation of dynaml expressions may fail because of several reasons:
If a dynaml expression cannot be resolved to a value, it is reported by the spiff merge
operation using the following layout:
(( <failed expression> )) in <file> <path to node> (<referred path>) <tag><issue>
(( min_ip("10") )) in source.yml node.a.[0] () *CIDR argument required
Cyclic dependencies are detected by iterative evaluation until the document is unchanged after a step. Nodes involved in a cycle are therefore typically reported just as unresolved node without a specific issue.
The order of the reported unresolved nodes depends on a classification of the problem, denoted by a dedicated tag. The following tags are used (in reporting order):
Etikett | Bedeutung |
---|---|
* | error in local dynaml expression |
@ | dependent or involved in cyclic dependencies |
- | subsequent error because of refering to a yaml node with an error |
Problems occuring during inline template processing are reported as nested problems. The classification is propagated to the outer node.
If a problem occurs in nested lamba calls the call stack together with the lamba function and is local binding is listed.
(( 2 + .func(2) )) in local/err.yaml value () *evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 2}
... evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 1}
... evaluation of lambda expression failed: lambda|x|->x > 0 ? _(x - 1) : *(template): {x: 0}
... resolution of template 'template' failed
(( z )) in local/err.yaml val ()*'z' not found
In case of parsing errors in dynaml expressions, the error location is shown now. If it is a multi line expression the line a character/symbol number in that line is show, otherwise the line numer is omitted.
((
2 ++ .func(2)
)) in local/err.yaml faulty () *parse error near line 2 symbol 2 - line 2 symbol 3: " "
Spiff provides a Go package ( spiffing
) that can be used to include spiff templates in Go programs.
An example program could look like this:
import (
"fmt"
"math"
"os"
"github.com/mandelsoft/spiff/dynaml"
"github.com/mandelsoft/spiff/spiffing"
)
func func_pow ( arguments [] interface {}, binding dynaml. Binding ) ( interface {}, dynaml. EvaluationInfo , bool ) {
info := dynaml . DefaultInfo ()
if len ( arguments ) != 2 {
return info . Error ( "pow takes 2 arguments" )
}
a , b , err := dynaml . NumberOperands ( arguments [ 0 ], arguments [ 1 ])
if err != nil {
return info . Error ( "%s" , err )
}
_ , i := a .( int64 )
if i {
r := math . Pow ( float64 ( a .( int64 )), float64 ( b .( int64 )))
if float64 ( int64 ( r )) == r {
return int64 ( r ), info , true
}
return r , info , true
} else {
return math . Pow ( a .( float64 ), b .( float64 )), info , true
}
}
var state = `
state: {}
`
var stub = `
unused: (( input ))
ages:
alice: (( pow(2,5) ))
bob: (( alice + 1 ))
`
var template = `
state:
<<<: (( &state ))
random: (( rand("[:alnum:]", 10) ))
ages: (( &temporary ))
example:
name: (( input )) # direct reference to additional values
sum: (( sum[ages|0|s,k,v|->s + v] ))
int: (( pow(2,4) ))
float: 2.1
pow: (( pow(1.1e1,2.1) ))
`
func Error ( err error ) {
if err != nil {
fmt . Fprintf ( os . Stderr , "Error: %s n " , err )
os . Exit ( 1 )
}
}
func main () {
values := map [ string ] interface {}{}
values [ "input" ] = "this is an input"
functions := spiffing . NewFunctions ()
functions . RegisterFunction ( "pow" , func_pow )
spiff , err := spiffing . New (). WithFunctions ( functions ). WithValues ( values )
Error ( err )
pstate , err := spiff . Unmarshal ( "state" , [] byte ( state ))
Error ( err )
pstub , err := spiff . Unmarshal ( "stub" , [] byte ( stub ))
Error ( err )
ptempl , err := spiff . Unmarshal ( "template" , [] byte ( template ))
Error ( err )
result , err := spiff . Cascade ( ptempl , []spiffing. Node { pstub }, pstate )
Error ( err )
b , err := spiff . Marshal ( result )
Error ( err )
newstate , err := spiff . Marshal ( spiff . DetermineState ( result ))
Error ( err )
fmt . Printf ( "==== new state === n " )
fmt . Printf ( "%s n " , string ( newstate ))
fmt . Printf ( "==== result === n " )
fmt . Printf ( "%s n " , string ( b ))
}
It supports