_ __ __
___ _ __ (_)/ _|/ _| _ _
/ __| '_ | | |_| |_ _| |_ _| |_
__ |_) | | _| _|_ _|_ _|
|___/ .__/|_|_| |_| |_| |_|
|_|
spiff++
は spiff のフォークであり、最新バージョンに基づいて spiff と互換性のある拡張機能を提供し、spiff ではまだ利用できない豊富な新機能を提供します。元の spiff プロジェクトによって提供されたすべての修正も spiff++ に組み込まれます。 spiff ソース ベースに戻ることはできないため、spiff++ の開発を継続するために、新しい独立した spiff++ リポジトリが作成されました。spiff は、コマンド ライン ツールであり、宣言型のドメイン内ハイブリッド YAML テンプレート システムです。通常のテンプレート システムは、テンプレート式を外部データ ソースから取得した値に置き換えることによってテンプレート ファイルを処理しますが、ドメイン内とは、テンプレート エンジンが処理されたテンプレートの構文と構造を認識していることを意味します。したがって、テンプレート式自体によって示される部分も含めて、処理されたドキュメントから直接テンプレート式の値を取得できます。
例えば:
resource :
name : bosh deployment
version : 25
url : (( "http://resource.location/bosh?version=" version ))
description : (( "This document describes a " name " located at " url ))
spiff は、外部値ソースのみを使用する代わりに、テンプレートを任意の数のマージ スタブとマージして最終ドキュメントを生成するマージ メカニズムを提供します。
これは、コマンド ライン ツールおよび宣言型 YAML テンプレート システムであり、デプロイメント マニフェスト (BOSH、Kubernetes、Landscaper マニフェストなど) を生成するために特別に設計されています。
CLI のほかに、任意の GO プログラム (Landscaper など) で spiff テンプレート処理を使用できる golang ライブラリがあります。
テンプレート エンジンは、構成可能な仮想ファイル システムまたはプロセス システムに基づいてファイル システムにアクセスして、コマンドを実行し、出力をテンプレート処理に組み込むことができるようにします。
コンテンツ:
<<if:
<<switch:
<<type:
<<for:
<<merge:
公式リリースの実行可能バイナリは、Darwin、Linux、および PowerPC マシン (および仮想マシン) 用の Github リリース経由でダウンロードできます。
spiff の依存関係の一部は、最後の公式リリース以降変更されており、これらの依存関係に対応するために spiff は更新されません。これらの依存関係は修正されるか、ローカル コード ベースにコピーされます。
spiff merge template.yml [template2.yml ...]
多数のテンプレート ファイルを 1 つのマニフェストに結合し、出力します。
テンプレート ファイルの詳細については「dynaml テンプレート言語」を参照し、より複雑な例については example/subdir を参照してください。
例:
spiff merge cf-release/templates/cf-deployment.yml my-cloud-stub.yml
ファイル名-
を使用して、標準入力から 1 つのファイルを読み取ることができます。 1回のみのご利用も可能です。これにより、パイプラインの一部として spiff を使用して、単一のストリームを処理することも、複数のテンプレート/スタブに基づいてストリームを処理することもできます。
テンプレート ファイル (最初の引数) は、 ---
のみを含む行で区切られた複数の YAML ドキュメントを含む複数ドキュメント ストリームである場合があります。各 YAML ドキュメントは、指定されたスタブ ファイルを使用して個別に処理されます。その結果、同じ順序で処理されたドキュメントのストリームが得られます。ドキュメントのルート ノードが一時ノードとしてマークされている場合、そのドキュメントは出力ストリームから省略されます。たとえば、これを使用して、 kubectl
で使用されるkubernetesマニフェストを生成できます。
merge
コマンドにはいくつかのオプションがあります。
オプション--partial
。このオプションを指定すると、spiff は不完全な式の評価を処理します。すべてのエラーは無視され、yaml ドキュメントの解決できない部分が文字列として返されます。
オプション--json
使用すると、出力は YAML ではなく JSON 形式になります。
オプション--path <path>
使用すると、処理された完全なドキュメントの代わりに、ネストされたパスを出力できます。
出力がリストの場合、オプション--split
すべてのリスト要素を個別のドキュメントとして出力します。 yaml形式では、通常どおり---
が区切り線として使用されます。 json形式は、一連のjsonドキュメントを 1 行に 1 つずつ出力します。
--select <field path>
を使用すると、処理されたドキュメントの出力用の専用フィールドを選択できます。
--evaluate <dynaml expression>
を使用すると、出力用に処理されたドキュメント上で指定された dynaml 式を評価できます。式は選択パスが適用される前に評価され、評価結果が処理されます。
オプション--state <path>
spiffの状態サポートを有効にします。指定されたファイルが存在する場合、そのファイルは、マージ処理用に、指定されたファイルの構成済みスタブ リストの先頭に配置されます。処理されたドキュメントの出力に加えて、 &state
マーカーでマークされたノードがフィルタリングされます。このフィルタリングされたドキュメントは、指定されたファイルの下に保存され、古い状態ファイルが.bak
拡張子付きで保存されます。これは、状態ユーティリティ ライブラリによって提供される手動マージと組み合わせて使用できます。
オプション--bindings <path>
を使用すると、yaml ファイルを指定できます。その内容は、処理用の追加バインディングの構築に使用されます。 yaml ドキュメントはマップで構成されている必要があります。各キーは追加のバインディングとして使用されます。バインディング ドキュメントは処理されず、値は定義どおりに使用されます。
オプション--tag <tag>:<path>
を使用すると、yaml ファイルを指定でき、その内容は事前定義されたグローバル タグの値として使用されます (「タグ」を参照)。タグには、 <tag>::<ref>
形式の参照式によってアクセスできます。バインディングとは対照的に、タグ付けされたコンテンツはドキュメント内のノードと競合せず、別の参照名前空間を使用します。
オプション--define <key>=<value>
(短縮形-D
) を使用すると、バインディング ファイルのバインディング値をオーバーライドして、追加のバインディング値をコマンド ラインで指定できます。このオプションは複数回出現する場合があります。
キーにドット ( .
) が含まれている場合、ディープ マップ値のフィールドを記述するパス式として解釈されます。ドット (およびドットの前の ) を
でエスケープして、フィールド名に含めることができます。
オプション--preserve-escapes
dynaml 式およびリスト/マップ マージ ディレクティブのエスケープを保存します。このオプションは、 spiffによる処理結果のさらなる処理ステップが意図されている場合に使用できます。
オプション--preserve-temporary
、一時としてマークされたフィールドを最終ドキュメントに保存します。
オプション--features=<featurelist>
は、この指定された機能を有効にします。古い動作と互換性のない新機能は、明示的に有効にする必要があります。通常、これらの機能は一般的な動作を壊すものではありませんが、以前は通常の値として使用されていた yaml 値に専用の解釈を導入します。
フォルダー ライブラリには、いくつかの便利なユーティリティ ライブラリが用意されています。これらは、このテンプレート エンジンの能力の例として使用することもできます。
spiff diff manifest.yml other-manifest.yml
2 つの展開マニフェスト間の構造的な違いを示します。ここでは、複数のドキュメントを含むストリームもサポートされています。違いがないことを示すには、両方のストリームのドキュメントの数が同一である必要があり、最初のストリームの各ドキュメントが、2 番目のストリームの同じインデックスを持つドキュメントと比較して違いがあってはなりません。見つかった相違点はドキュメントごとに個別に表示されます。
基本的な比較ツールやbosh diff
とは異なり、このコマンドは展開マニフェストのセマンティックな知識を持ち、単なるテキストベースではありません。たとえば、マニフェストではジョブの順序が重要であるため、2 つのマニフェストが一部のジョブが異なる順序でリストされている点を除いて同じである場合、 spiff diff
これを検出します。一方、たとえば、2 つのマニフェストがリソース プールの順序のみで異なる場合、リソース プールの順序はデプロイメントにとって実際には重要ではないため、差分が返されて空になります。
また、 bosh diff
とは異なり、このコマンドはどちらのファイルも変更しません。
これは、あるデプロイメントと次のデプロイメントの違いを確認するために使用されます。
一般的なフロー:
$ 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
convert
サブコマンドは、入力ファイルを JSON に変換するか、単にフィールドの順序を正規化するために使用できます。使用可能なオプションは、 merge
サブコマンドの意味に応じて、 --json
、 --path
、 --split
、または--select
です。
spiff encrypt secret.yaml
encrypt
サブコマンドは、 encrypt
dynaml 関数に従ってデータを暗号化または復号化するために使用できます。パスワードは 2 番目の引数として指定することも、環境変数SPIFF_ENCRYPTION_KEY
から取得することもできます。最後の引数は、暗号化メソッドを渡すために使用できます ( encrypt
関数を参照)。
データは指定されたファイルから取得されます。 -
指定すると、stdin から読み取られます。
オプション-d
が指定されている場合、データは復号化されます。それ以外の場合、データは yaml ドキュメントとして読み取られ、暗号化された結果が出力されます。
古い動作と互換性のない新機能は、明示的に有効にする必要があります。通常、これらの機能は共通の動作を壊すものではありませんが、以前は通常の値として使用されていた yaml 値に専用の解釈を導入するため、既存のユースケースを壊す可能性があります。
現在、次の機能フラグがサポートされています。
特徴 | 以来 | 州 | 意味 |
---|---|---|---|
interpolation | 1.7.0-ベータ-1 | アルファ | yaml 文字列の一部としての dynaml |
control | 1.7.0-ベータ-4 | アルファ | yamlベースの制御構造 |
アクティブな機能フラグは、 dynaml関数features()
文字列のリストとして使用してクエリできます。この関数が文字列引数を指定して呼び出された場合、指定された機能が現在有効かどうかを返します。
機能は、コマンド ラインで--features
オプションを使用するか、Go ライブラリでWithFeatures
関数を使用するか、一般に環境変数SPIFF_FEATURES
機能リストに設定することで有効にできます。この設定は常にデフォルトとして使用されます。 go ライブラリのPlain()
spiff 設定を使用すると、すべての環境変数が無視されます。
機能は、名前で指定することも、名前の前にプレフィックスno
を付けて無効にすることもできます。
library フォルダーには、いくつかの便利なspiffテンプレート ライブラリが含まれています。これらは基本的に、マージ処理用のユーティリティ関数を提供するためにマージ ファイル リストに追加される単なるスタブです。
Spiff は、「dynaml」(動的 yaml) と呼ばれる、宣言型のロジックフリーのテンプレート言語を使用します。
すべての dynaml ノードは YAML ノードに解決されることが保証されています。文字列補間ではありません。これにより、開発者は、結果のテンプレートで値がどのように表示されるかを考える必要がなくなります。
dynaml ノードは、2 つの括弧(( <dynaml> ))
で囲まれた式を示す文字列として .yml ファイルに表示されます。これらは、マップの値またはリストのエントリとして使用できます。式は複数行にまたがる場合があります。いずれの場合も、yaml 文字列値は改行で終わってはなりません(たとえば、 |-
使用する)
括弧で囲まれた値をdynaml式として解釈せず、そのまま出力に保持する必要がある場合は、左括弧の直後に感嘆符を使用してエスケープできます。
たとえば、 ((! .field ))
文字列値(( .field ))
にマップされ、 ((!! .field ))
文字列値((! .field ))
にマップされます。
以下は、dynaml 式の完全なリストです。
(( foo ))
現在のテンプレート内で最も近い 'foo' キー (つまり、字句スコープ) を探し、それを取り込みます。
例えば:
fizz :
buzz :
foo : 1
bar : (( foo ))
bar : (( foo ))
foo : 3
bar : (( foo ))
この例は次のように解決されます。
fizz :
buzz :
foo : 1
bar : 1
bar : 3
foo : 3
bar : 3
以下は、キー名がマージされる値と同じであるため、解決されません。
foo : 1
hi :
foo : (( foo ))
(( foo.bar.[1].baz ))
最も近い 'foo' キーを探し、そこから.bar.[1].baz
まで進みます。
パスは、ドットで区切られた一連のステップです。ステップは、マップの単語、またはリストのインデックス付けのための大括弧で囲まれた数字のいずれかです。インデックスは負の値になる場合があります (マイナスの後に数字が続きます)。負のインデックスはリストの最後から取得されます (有効なインデックス = インデックス + 長さ(リスト))。
パスを解決できない場合は、評価エラーが発生します。参照が提供されない場合があることが予想される場合は、「||」と組み合わせて使用する必要があります。 (下記を参照) 解像度を保証します。
注: dynaml 文法は、通常のインデックス構文を有効にするために再加工されました。 foo.bar.[1]
の代わりにfoo.bar[1]
使用できるようになりました。
注: 参照は常にテンプレートまたはスタブ内にあり、順序は関係ありません。別の動的ノードを参照して、それが解決されたと想定することができます。依存ノードが解決されると、参照ノードも最終的に解決されます。
例えば:
properties :
foo : (( something.from.the.stub ))
something : (( merge ))
これは、「何か」が解決可能である限り、そしてそれが次のようなものをもたらす限り、解決されます。
from :
the :
stub : foo
パスがドット ( .
) で始まる場合、パスは常にドキュメントのルートから評価されます。ドキュメント ルートがリストの場合、最初のマップ レベルが.__map
で始まるパス式を解決するために使用されます。これを使用すると、リスト エントリが追加されると変更される可能性がある独自のリスト インデックス ( .[1].path
など) を使用する必要性を回避できます。
name
フィールドを含むマップで構成されるリスト エントリは、パス コンポーネントとしての名前値によって直接アドレス指定できます。
注: これは、リスト ドキュメントの絶対パスにも機能します。
例えば:
アリスの時代
list :
- name : alice
age : 25
list[0].age
の代わりにパスlist.alice.age
を使用して参照できます。
デフォルトでは、 name
という名前のフィールドがキー フィールドとして使用されます。別のフィールドをキー フィールドとして使用する必要がある場合は、フィールド名の前にキーワードkey:
を付けることで、1 つのリスト エントリ内でそのフィールドをキーとしてマークできます。このキーワードは処理によって削除され、最終的な処理結果の一部にはなりません。
例えば:
list :
- key:person : alice
age : 25
alice : (( list.alice ))
に解決されます
list :
- person : alice
age : 25
alice :
person : alice
age : 25
この新しいキー フィールドは、リストの結合中にも観察されます。
選択したキー フィールドが!
で始まる場合、キー機能は無効になります。有効なフィールド名からも感嘆符が削除されます。
キー フィールドの値が不一致の場合も、無効になります。
(( foo.[bar].baz ))
最も近い「foo」キーを探し、そこから式bar
で記述されたフィールド、そして .baz までたどります。
最後のセクションで説明したように、インデックスは整数定数 (スペースなし) にすることができます。ただし、任意の dynaml 式 (整数でもスペースを含む) である場合もあります。式が文字列として評価される場合、専用のフィールドが検索されます。式が整数に評価される場合、このインデックスを持つ配列要素がアドレス指定されます。インデックス演算子の前のドット ( .
) はオプションです。
例えば:
properties :
name : alice
foo : (( values.[name].bar ))
values :
alice :
bar : 42
これにより、 foo
値42
に解決されます。動的インデックスは式の最後 ( .bar
なし) にある場合もあります。
基本的に、これは eval("values." name ".bar") のようなものを表現するより簡単な方法です。
式がリストとして評価される場合、リスト要素 (文字列または整数) は、より深いフィールドにアクセスするためのパス要素として使用されます。
例えば:
properties :
name :
- foo
- bar
foo : (( values.[name] ))
values :
foo :
bar : 42
foo
再び値42
に解決します。
注: インデックス演算子は、ルート要素 ( .[index]
) でも使用できます。
連続するリストに複数のカンマ区切りのインデックスを指定することが可能です ( foo[0][1]
は `foo[0,1] と同等です)。このような場合、インデックスは再びリストにならない可能性があります。
(( list.[1..3] ))
スライス式を使用すると、リスト式から専用のサブリストを抽出できます。 start ..
end の範囲は、インデックスstartからendまでの要素を含む長さend-start+1のリストを抽出します。開始インデックスが負の場合、スライスはリストの末尾からlength+startからlength+endまで取得されます。終了インデックスが開始インデックスより小さい場合、結果は空の配列になります。
例えば:
list :
- a
- b
- c
foo : (( list.[1..length(list) - 1] ))
開始インデックスまたは終了インデックスは省略される場合があります。その後、リストの実際のサイズに応じて選択されます。したがってlist.[1..length(list)]
list.[1..]
と同等です。
foo
リスト[b,c]
に対して評価します。
(( 1.2e4 ))
数値リテラルは、整数と浮動小数点値でサポートされています。
(( "foo" ))
文字列リテラル。すべての JSON 文字列エンコーディングがサポートされています (たとえば、 n
、 "
またはuxxxx
)。
(( [ 1, 2, 3 ] ))
リストリテラル。リスト要素は式である場合もあります。特別なリスト リテラル[1 .. -1]
があり、これを使用して、増加または減少する数値範囲をリストに解決できます。
例えば:
list : (( [ 1 .. -1 ] ))
収量
list :
- 1
- 0
- -1
(( { "alice" = 25 } ))
マップ リテラルは、dynaml 式の一部としてマップを記述するために使用できます。キーと値の両方が式である場合もあり、キー式は文字列として評価される必要があります。このようにして、非静的キーを使用してマップを作成できます。 yaml 構文と競合する可能性があるため、yaml で使用される通常のコロン:
文字の代わりに代入演算子=
選択されています。
マップ リテラルは、コンマ,
で区切られた任意の数のフィールド割り当てで構成されます。
例えば:
name : peter
age : 23
map : (( { "alice" = {}, name = age } ))
収量
name : peter
age : 23
map :
alice : {}
peter : 23
式に基づいてリストを作成するもう 1 つの方法は、関数makemap
およびlist_to_map
です。
(( ( "alice" = 25 ) alice ))
任意の式の前に、任意の数の明示的なスコープリテラルを置くことができます。スコープ リテラルは、式の相対参照解決に使用できる値を持つマップを記述します (静的スコープ)。指定された名前に対して追加のローカル バインディングを作成します。
スコープ リテラルは、コンマ,
で区切られた任意の数のフィールド割り当てで構成されます。キーと値は式によって指定されますが、キー式は文字列として評価される必要があります。すべての式は次の外側のスコープで評価されます。これは、スコープ内の後の設定では、同じスコープ リテラル内の以前の設定を使用できないことを意味します。
例えば:
scoped : (( ( "alice" = 25, "bob" = 26 ) alice + bob ))
収量
scoped : 51
フィールド名は記号 ( $
name ) で示される場合もあります。
(( foo bar ))
一連の dynaml 式を連結するために使用される連結式。
(( "foo" bar ))
連結 (bar は別の dynaml expr です)。任意の dynaml 式で指定された単純な値 (文字列、整数、およびブール値) のシーケンスを連結できます。
例えば:
domain : example.com
uri : (( "https://" domain ))
この例では、 uri
値"https://example.com"
に解決されます。
(( [1,2] bar ))
式としてのリストの連結 (bar は別の dynaml expr です)。任意の dynaml 式で指定された任意のリストのシーケンスを連結できます。
例えば:
other_ips : [ 10.0.0.2, 10.0.0.3 ]
static_ips : (( ["10.0.1.2","10.0.1.3"] other_ips ))
この例では、 static_ips
値[ 10.0.1.2, 10.0.1.3, 10.0.0.2, 10.0.0.3 ]
に解決されます。
2 番目の式がリスト以外の値 (整数、ブール値、文字列、またはマップ) に評価された場合、その値は最初のリストに追加されます。
例えば:
foo : 3
bar : (( [1] 2 foo "alice" ))
bar
のリスト[ 1, 2, 3, "alice" ]
を生成します。
(( map1 map2 ))
式としてのマップの連結。任意の dynaml 式によって指定されたマップの任意のシーケンスを連結できます。これにより、エントリがマージされます。同じキーを持つエントリは左から右に上書きされます。
例えば:
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat : (( foo bar ))
収量
foo :
alice : 24
bob : 25
bar :
bob : 26
paul : 27
concat :
alice : 24
bob : 26
paul : 27
(( auto ))
コンテキストに応じた自動値計算。
リソース プールの「サイズ」属性において、これは、現在のリソース プール内に存在することを宣言するすべてのジョブの合計インスタンスに基づいて計算することを意味します。
例えば:
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
この場合、リソース プール サイズは「5」に解決されます。
(( merge ))
マージされるスタブ ファイルから現在のパスを取得します。
例えば:
foo :
bar :
baz : (( merge ))
最初のスタブ、または 2 番目のスタブなどからfoo.bar.baz
を取り込もうとし、それを提供する最後のスタブから値を返します。
対応する値が定義されていない場合は、nil を返します。これは、参照式と同じセマンティクスを持ちます。 nil マージは未解決のテンプレートです。 ||
参照してください。 。
<<: (( merge ))
マップまたはリストと、一部のスタブで見つかった同じ要素のコンテンツとのマージ。
** 注意 ** この形式のmerge
には互換性の問題があります。 1.0.8 より前のバージョンでは、この式は解析されず、キー<<:
の存在のみが関係していました。したがって、 <<: (( merge ))
<<: (( merge || nil ))
が頻繁に使用されます。最初のバリアントでは、少なくとも 1 つのスタブにコンテンツが必要です (マージ オペレーターの場合は常にそうです)。この式は正しく評価されるようになりましたが、これにより、最初のバリアントを使用するが 2 番目のバリアントを意味する既存のマニフェスト テンプレート セットが壊れてしまいます。したがって、このケースはオプションのマージを記述するために明示的に処理されます。本当に必要なマージを意味する場合は、追加の明示的な修飾子を指定する必要があります。
注: マージ式を配置するために<<:
挿入フィールドを使用する代わりに、 <<<:
を使用することもできるようになりました。これにより、spiff のような yaml ドキュメントに通常の yaml パーサーを使用できるようになります。 <<:
は下位互換性のために残されています。使用することができます ( (( merge required ))
)。
マージ キーをマージ ディレクティブではなく通常のキーとして解釈する必要がない場合は、感嘆符 ( !
) でエスケープできます。
たとえば、マップ キー<<<!
結果は文字列キー<<<
と<<<!!
文字列キー<<<!
値.yml
foo :
a : 1
b : 2
テンプレート.yml
foo :
<< : (( merge ))
b : 3
c : 4
spiff merge template.yml values.yml
結果は次のとおりです。
foo :
a : 1
b : 2
c : 4
値.yml
foo :
- 1
- 2
テンプレート.yml
foo :
- 3
- << : (( merge ))
- 4
spiff merge template.yml values.yml
結果は次のとおりです。
foo :
- 3
- 1
- 2
- 4
- <<: (( merge on key ))
spiff
マップのリストをキー フィールドとマージできます。これらのリストは、キー フィールドの値をキーとしてマップのように扱われます。デフォルトでは、キーname
が使用されます。ただし、セレクターを使用すると、リストマージon
任意のキー名を指定できます。
例えば:
list :
- << : (( merge on key ))
- key : alice
age : 25
- key : bob
age : 24
と合併しました
list :
- key : alice
age : 20
- key : peter
age : 13
収量
list :
- key : peter
age : 13
- key : alice
age : 20
- key : bob
age : 24
(挿入マージ式で要求されたように) 新しいエントリの挿入が望まれず、既存のエントリのオーバーライドのみが必要な場合は、1 つの既存のキー フィールドにタグkey:
これは、非標準のキー名を示すためです。たとえば、 - key:key: alice
。
<<: (( merge replace ))
既存のコンテンツを詳細にマージする代わりに、要素の完全なコンテンツをスタブで見つかったコンテンツに置き換えます。
値.yml
foo :
a : 1
b : 2
テンプレート.yml
foo :
<< : (( merge replace ))
b : 3
c : 4
spiff merge template.yml values.yml
結果は次のとおりです。
foo :
a : 1
b : 2
値.yml
foo :
- 1
- 2
テンプレート.yml
foo :
- << : (( merge replace ))
- 3
- 4
spiff merge template.yml values.yml
結果は次のとおりです。
foo :
- 1
- 2
<<: (( foo ))
同じテンプレートまたはスタブ内にあるマップとリストのマージ。
foo :
a : 1
b : 2
bar :
<< : (( foo )) # any dynaml expression
b : 3
得られるもの:
foo :
a : 1
b : 2
bar :
a : 1
b : 3
この式は、実際のリストに新しいエントリを追加するだけです。既存のエントリとマージ式で記述されたコンテンツはマージされません。
bar :
- 1
- 2
foo :
- 3
- << : (( bar ))
- 4
得られるもの:
bar :
- 1
- 2
foo :
- 3
- 1
- 2
- 4
この一般的な使用例は、静的 IP または範囲のリストを IP のリストにマージすることです。もう 1 つの方法は、単一の連結式を使用することです。
<<: (( merge foo ))
マップまたはリストと、一部のスタブで見つかった任意の要素のコンテンツとのマージ (リダイレクト マージ)。一部のスタブで見つかった同じ名前の要素とのこれ以上の (深い) マージは行われません。 (リストのディープマージには、フィールドname
を持つマップが必要です)
リダイレクトマージは直接フィールド値としても使用できます。これらは(( merge replace foo ))
のような置換マージと組み合わせることができます。
値.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
テンプレート.yml
foo :
<< : (( merge bar))
b : 3
c : 4
spiff merge template.yml values.yml
結果は次のとおりです。
foo :
a : 1
b : 2
c : 4
一部のスタブ内の別の要素とマージを行う別の方法も、従来の方法で行うことができます。
値.yml
foo :
a : 10
b : 20
bar :
a : 1
b : 2
テンプレート.yml
bar :
<< : (( merge ))
b : 3
c : 4
foo : (( bar ))
ただし、このシナリオでは、マージでは元の要素名を使用したディープ マージが引き続き実行されます。したがって、 spiff merge template.yml values.yml
次のようになります。
bar :
a : 1
b : 2
c : 4
foo :
a : 10
b : 20
c : 4
値.yml
foo :
- 10
- 20
bar :
- 1
- 2
テンプレート.yml
foo :
- 3
- << : (( merge bar ))
- 4
spiff merge template.yml values.yml
結果は次のとおりです。
foo :
- 3
- 1
- 2
- 4
<<: (( merge none ))
リダイレクトマージの参照が定数none
に設定されている場合、マージはまったく行われません。この式は常に nil 値を返します。
例: のために
テンプレート.yml
map :
<< : (( merge none ))
value : notmerged
値.yml
map :
value : merged
spiff merge template.yml values.yml
結果は次のとおりです。
map :
value : notmerged
これは、 stub
関数を使用して上流スタブの専用部分にアクセスする明示的なフィールド マージに使用できます。
例えば:
テンプレート.yml
map :
<< : (( merge none ))
value : (( "alice" "+" stub(map.value) ))
値.yml
map :
value : bob
spiff merge template.yml values.yml
結果は次のとおりです。
test :
value : alice+bob
これは専用フィールドでも機能します。
テンプレート.yml
map :
value : (( merge none // "alice" "+" stub() ))
値.yml
map :
value : bob
spiff merge template.yml values.yml
結果は次のとおりです。
test :
value : alice+bob
(( a || b ))
a を解決できない場合は、a または b を使用します。
例えば:
foo :
bar :
- name : some
- name : complicated
- name : structure
mything :
complicated_structure : (( merge || foo.bar ))
これにより、 mything.complicated_structure
へのマージが試行されます。マージできない場合は、 foo.bar
で指定されたデフォルトが使用されます。
演算子//
、 a
有効な値 ( ~
と等しくない) に解決できるかどうかをさらにチェックします。
(( 1 + 2 * foo ))
Dynaml 式を使用して、整数および浮動小数点の算術計算を実行できます。サポートされている演算は、 +
、 -
、 *
、および/
です。モジュロ演算子 ( %
) は整数オペランドのみをサポートします。
例えば:
値.yml
foo : 3
bar : (( 1 + 2 * foo ))
spiff merge values.yml
bar
に対して7
生成します。これは連結と組み合わせることができます (dynaml 式では計算が連結よりも優先されます)。
foo : 3
bar : (( foo " times 2 yields " 2 * foo ))
結果は、文字列3 times 2 yields 6
。
(( "10.10.10.10" - 11 ))
整数の算術演算に加えて、IP アドレスと cidr の加算と減算を使用することもできます。
例えば:
ip : 10.10.10.10
range : (( ip "-" ip + 247 + 256 * 256 ))
収量
ip : 10.10.10.10
range : 10.10.10.10-10.11.11.1
減算は 2 つの IP アドレスまたは cidr に対しても機能し、2 つの IP アドレス間の IP アドレスの数を計算します。
例えば:
diff : (( 10.0.1.0 - 10.0.0.1 + 1 ))
値は 256 になります。IP アドレス定数は dynaml 式で直接使用できます。これらは暗黙的に文字列に変換され、操作で必要な場合は IP アドレスに戻されます。
乗算と除算を使用して、CIDR の IP 範囲のシフトを処理できます。分割を使用すると、ネットワークを分割できます。ネットワーク サイズは、元の CIDR を下回る少なくとも専用の数のサブネットを許可するために増加します。その後、乗算を使用して、同じサイズの n 番目の次のサブネットを取得できます。
例えば:
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
収量
subnet : 10.1.2.0/28
next : 10.1.2.32/28
さらに、IPv4 CIDR で動作する関数もあります。
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") ))
収量
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 は、比較演算子<
、 <=
、 ==
、 !=
、 >=
および>
をサポートします。比較演算子は整数値を処理します。等価性のチェックはリストとマップでも機能します。結果は常にブール値です。条件を否定するには、単項 not 演算子 ( !
) を使用できます。
さらに、条件に応じて式を評価するために使用できる 3 項条件演算子?:
もあります。最初のオペランドは条件として使用されます。式は、条件が true の場合は 2 番目のオペランドまで評価され、そうでない場合は 3 番目のオペランドまで評価されます。
例えば:
foo : alice
bar : bob
age : 24
name : (( age > 24 ? foo :bar ))
プロパティname
の値bob
が生成されます。
式が次のように評価される場合、式はfalse
とみなされます。
false
それ以外の場合はtrue
とみなされます
述べる
完全な式が引用符で囲まれた文字列値でない場合、記号:
を使用すると、yaml 構文と衝突する可能性があります。
演算子-or
および-and
使用すると、比較演算子を組み合わせて、より複雑な条件を作成できます。
述べる:
より伝統的な演算子記号||
(および&&
) は演算子||
であるため、ここでは使用できません。論理演算には当てはまらない、別のセマンティクスを持つ dynaml 内にすでに存在します。式false || true
、値に関係なく、最初のオペランドが定義されている場合はそのオペランドを生成するため、 false
と評価されます。可能な限り互換性を持たせるために、これを変更することはできません。 and
、裸のシンボルor
と を使用することはできません。これは、そのような名前を持つ参照の連結が無効になるためです。
(( 5 -or 6 ))
-or
または-and
演算子の両側が整数値に評価される場合、ビット単位の演算が実行され、結果は再び整数になります。したがって、式5 -or 6
7
と評価されます。
Dynaml は、事前定義された関数のセットをサポートしています。関数は通常次のように呼び出されます
result : (( functionname(arg, arg, ...) ))
追加の関数は、ラムダ式を使用して yaml ドキュメントの一部として定義できます。関数名は、グループ化された式、またはラムダ式をホストするノードへのパスのいずれかになります。
(( format( "%s %d", alice, 25) ))
dynaml 式で指定された引数に基づいて文字列をフォーマットします。この関数には 2 番目の種類があります。error error
エラー メッセージをフォーマットし、評価を失敗に設定します。
(( join( ", ", list) ))
指定された区切り文字列を使用して、リストのエントリまたは直接値を単一の文字列値に結合します。結合する引数は、リストとして評価される dynaml 式にすることができ、その値も文字列または整数、または文字列または整数値になります。
例えば:
alice : alice
list :
- foo
- bar
join : (( join(", ", "bob", list, alice, 10) ))
join
の文字列値bob, foo, bar, alice, 10
が生成されます。
(( split( ",", string) ))
文字列を専用のセパレータで分割します。結果はリストです。区切り文字列の代わりに整数値を指定すると、指定された文字列が長さ制限された文字列のリストに分割されます。長さはバイトではなくルーン単位で計算されます。
例えば:
list : (( split("," "alice, bob") ))
limited : (( split(4, "1234567890") ))
得られるもの:
list :
- alice
- ' bob '
limited :
- " 1234 "
- " 5678 "
- " 90 "
オプションの 3 番目の引数を指定できます。返されるリスト エントリの数を制限します。値 -1 を指定すると、リストの長さは無制限になります。
正規表現を区切り文字列として使用する必要がある場合は、 split_match
関数を使用できます。
(( trim(string) ))
文字列または文字列のリストのすべての要素をトリミングします。オプションの 2 番目の文字列引数があります。切り取る文字のセットを指定するために使用できます。デフォルトのカット セットはスペースとタブ文字で構成されます。
例えば:
list : (( trim(split("," "alice, bob")) ))
得られるもの:
list :
- alice
- bob
(( element(list, index) ))
インデックスによって指定された専用のリスト要素を返します。
例えば:
list : (( trim(split("," "alice, bob")) ))
elem : (( element(list,1) ))
得られるもの:
list :
- alice
- bob
elem : bob
(( element(map, key) ))
キーによって指定された専用のマップ フィールドを返します。
map :
alice : 24
bob : 25
elem : (( element(map,"bob") ))
得られるもの:
map :
alice : 24
bob : 25
elem : 25
この関数は、ドット (.) を含むキーも処理できます。
(( compact(list) ))
空のエントリを除外してリストをフィルタリングします。
例えば:
list : (( compact(trim(split("," "alice, , bob"))) ))
得られるもの:
list :
- alice
- bob
(( uniq(list) ))
Uniq は重複のないリストを提供します。
例えば:
list :
- a
- b
- a
- c
- a
- b
- 0
- " 0 "
uniq : (( uniq(list) ))
フィールドuniq
の結果:
uniq :
- a
- b
- c
- 0
(( contains(list, "foobar") ))
リストに専用の値が含まれているかどうかを確認します。値はリストまたはマップである場合もあります。
例えば:
list :
- foo
- bar
- foobar
contains : (( contains(list, "foobar") ))
得られるもの:
list :
- foo
- bar
- foobar
contains : true
この関数contains
文字列に対しても機能してサブ文字列を検索したり、マップを検索してキーを検索したりすることもできます。このような場合、要素は文字列である必要があります。
例えば:
contains : (( contains("foobar", "bar") ))
true
返します。
(( basename(path) ))
関数basename
パスの最後の要素の名前を返します。引数は通常のパス名または URL のいずれかです。
例えば:
pathbase : (( basename("alice/bob") ))
urlbase : (( basename("http://foobar/alice/bob?any=parameter") ))
得られるもの:
pathbase : bob
urlbase : bob
(( dirname(path) ))
関数dirname
パスの親ディレクトリを返します。引数は通常のパス名または URL のいずれかです。
例えば:
pathbase : (( dirname("alice/bob") ))
urlbase : (( dirname("http://foobar/alice/bob?any=parameter") ))
得られるもの:
pathbase : alice
urlbase : /alice
(( parseurl("http://github.com") ))
この関数は URL を解析し、URL のすべての要素を含むマップを生成します。 port
、 userinfo
、およびpassword
フィールドはオプションです。
例えば:
url : (( parseurl("https://user:[email protected]:443/mandelsoft/spiff?branch=master&tag=v1#anchor") ))
得られるもの:
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") ))
リストに専用値が含まれているかどうかを確認し、最初の一致のインデックスを返します。値もリストまたはマップです。エントリが見つからなかった場合-1
が返されます。
例えば:
list :
- foo
- bar
- foobar
index : (( index(list, "foobar") ))
利回り:
list :
- foo
- bar
- foobar
index : 2
関数index
サブ文字列を探すために文字列でも動作します。
例えば:
index : (( index("foobar", "bar") ))
3
。
(( lastindex(list, "foobar") ))
lastindex
関数はindex
のように機能しますが、最後の発生のインデックスは返されます。
関数sort
を使用して、整数または文字列リストを並べ替えることができます。ソート操作は安定しています。
例えば:
list :
- alice
- foobar
- bob
sorted : (( sort(list) ))
sorted
た収穫量
- alice
- bob
- foobar
他のタイプをソートする必要がある場合、特にリストやマップなどの複雑なタイプ、または異なる比較ルールが必要な場合、比較関数をオプションの2番目の引数として指定できます。比較関数は、2つの議論を受け取るラムダの表現でなければなりません。結果タイプは、 aがbより少ないかどうかを示すinteger
またはbool
でなければなりません。整数が返された場合はそうあるべきです
例えば:
list :
- alice
- foobar
- bob
sorted : (( sort(list, |a,b|->length(a) < length(b)) ))
sorted
た収穫量
- bob
- alice
- foobar
(( replace(string, "foo", "bar") ))
文字列内のサブ文字列のすべての発生を交換用文字列で置き換えます。オプションの4番目の整数引数では、置換の数を制限できます(-1平均無制限)。
例えば:
string : (( replace("foobar", "o", "u") ))
fuubar
を生成します。
正規表現を検索文字列として使用する場合は、関数replace_match
を使用することができます。ここで、検索文字列は正規表現として評価されます。サブ式をコナテインする可能性があります。これらの一致は、交換用文字列で使用できます
例えば:
string : (( replace_match("foobar", "(o*)b", "b${1}") ))
fbooar
を生成します。
置換引数は、ラムダ関数でもある可能性があります。この場合、すべての一致について、関数が呼び出され、交換値が決定されます。単一の入力引数は、実際のサブ式マッチのリストです。
例えば:
string : (( replace_match("foobar-barfoo", "(o*)b", |m|->upper(m.[1]) "b" ) ))
fOObar-barfoo
を生み出します。
(( substr(string, 1, 2) ))
特定の開始インデックスからオプションのエンドインデックス(排他的)までの文字列からスタブ文字列を抽出します。エンドインデックスが与えられない場合、文字列の最後までサブストルブが抽出されます。どちらのインデックスも負になる可能性があります。この場合、それらは文字列の端から取られます。
例えば:
string : " foobar "
end1 : (( substr(string,-2) ))
end2 : (( substr(string,3) ))
range : (( substr(string,1,-1) ))
に評価します
string : foobar
end1 : ar
end2 : bar
range : ooba
(( match("(f.*)(b.*)", "xxxfoobar") ))
特定の文字列値の正規式の一致を返します。一致は、正規表現に含まれるサブ式の一致した値のリストです。インデックス0は、完全な正規表現の一致を指します。文字列値が空のリストと一致しない場合。
例えば:
matches : (( match("(f.*)*(b.*)", "xxxfoobar") ))
利回り:
matches :
- foobar
- foo
- bar
タイプ整数の3番目の引数は、最大nの繰り返しのマルチマッチを要求するために与えられる場合があります。値が負の場合、すべての繰り返しが報告されます。結果は、それぞれ上記の形式のすべての一致のリストです。
(( keys(map) ))
マップで使用されるキーのソートされたリストを決定します。
例えば:
map :
alice : 25
bob : 25
keys : (( keys(map) ))
利回り:
map :
alice : 25
bob : 25
keys :
- alice
- bob
(( length(list) ))
リストの長さ、マップ、または文字列値を決定します。
例えば:
list :
- alice
- bob
length : (( length(list) ))
利回り:
list :
- alice
- bob
length : 2
(( base64(string) ))
関数base64
特定の文字列のbase64エンコードを生成します。 base64_decode
base64エンコードされた文字列をデコードします。
例えば:
base64 : (( base64("test") ))
test : (( base64_decode(base64)))
に評価します
base54 : dGVzdA==
test : test
オプションの2番目の引数を使用して、最大行長を指定できます。この場合、結果はマルチライン文字列になります。
(( hash(string) ))
関数hash
与えられた文字列のいくつかの種類のハッシュを生成します。デフォルトでは、 sha256
ハッシュが生成されます。オプションの2番目の引数は、ハッシュタイプを指定します。考えられるタイプは、 md4
、 md5
、 sha1
、 sha224
、 sha256
、 sha384
、 sha2512
、 sha512/224
またはsha512/256
です。
md5
ハッシュは、非推奨Finctio md5(string)
によって引き続き生成されます。
例えば:
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") ))
に評価します
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) ))
関数bcrypt
指定されたコスト係数を使用して、指定された文字列に対してBcryptパスワードハッシュを生成します(欠落している場合はデフォルトは10になりました)。
例えば:
hash : (( bcrypt("password", 10) ))
に評価します
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
(( bcrypt_check("password", hash) ))
関数bcrypt_check
特定のBCRYPTハッシュに対してパスワードを検証します。
例えば:
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
に評価します
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : true
(( md5crypt("password") ))
関数md5crypt
指定された文字列のApache MD5暗号化されたパスワードハッシュを生成します。
例えば:
hash : (( md5crypt("password") ))
に評価します
hash : $apr1$3Qc1aanY$16Sb5h7U1QrcqwZbDJIYZ0
(( md5crypt_check("password", hash) ))
関数md5crypt_check
特定のApache MD5暗号化ハッシュに対するパスワードを検証します。
例えば:
hash : $2a$10$b9RKb8NLuHB.tM9haPD3N.qrCsWrZy8iaCD4/.cCFFCRmWO4h.koe
valid : (( bcrypt_check("password", hash) ))
に評価します
hash : $apr1$B77VuUUZ$NkNFhkvXHW8wERSRoi74O1
valid : true
(( decrypt("secret") ))
この関数は、暗号化された秘密をSpiff YAMLファイルに保存するために使用できます。処理された結果には、復号化された値が含まれます。すべてのノードタイプは、完全なマップとリストを含む、暗号化および復号化できます。
復号化のパスワードは、2番目の引数として指定するか、環境変数SPIFF_ENCRYPTION_KEY
で指定することができます。
オプションの最後の引数では、暗号化方法を選択できます。これまでにサポートされている唯一の方法は3DES
です。 Spiff Libraryが提供する暗号化方法登録を使用して、専用のSpiffバージョンには他の方法を追加できます。
値はencrypt("secret")
関数を使用して暗号化できます。
例えば:
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) ))
のようなものに評価されます
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) ))
関数rand
ランダム値を生成します。最初の議論は、どのような値の要求が要求されるかを決定します。引数なしで、 int64
範囲で正の乱数を生成します。
引数タイプ | 結果 |
---|---|
整数 | 陽性nの範囲[0、 n )および( n 、0]の負のnの整数値[0、n) |
ブール | ブール値 |
弦 | ルーンが指定された文字範囲にある1つのルーン文字列、regexpに使用可能なキャラクタークラスまたは文字範囲の任意の組み合わせを使用できます。追加の長さの引数が指定されている場合、結果の文字列には与えられた長さがあります。 |
例えば:
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) ))
に評価します
int : 8037669378456096839
int10 : 7
neg10 : -5
bool : true
string : ghhjAYPMlD
upper : LBZQFRSURL
alnum : 0p6KS7EhAj
punct : ' &{;,^])"(# '
(( type(foobar) ))
関数type
指定された式のタイプを示す文字列を生成します。
例えば:
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(~~) ))
タイプをに評価します
types :
- int : int
- float : float
- bool : bool
- string : string
- list : list
- map : map
- lambda : lambda
- template : template
(( defined(foobar) ))
関数defined
、式を正常に評価できるかどうかをチェックします。式を評価できる場合は、ブール値をtrue
生成し、それ以外の場合はfalse
なります。
例えば:
zero : 0
div_ok : (( defined(1 / zero ) ))
zero_def : (( defined( zero ) ))
null_def : (( defined( null ) ))
に評価します
zero : 0
div_ok : false
zero_def : true
null_def : false
この関数は、条件付き演算子を組み合わせて使用して、別の式の解決可能性に応じて式を評価できます。
(( valid(foobar) ))
function valid
、式を正常に評価できるかどうかをチェックし、定義された値に評価できないnil
どうかを確認します。式を評価できる場合は、ブール値をtrue
生成し、それ以外の場合はfalse
なります。
例えば:
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 ) ))
に評価します
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) ))
関数は、指定された引数が未定義またはnil
ある場合、エラーを生成するrequire
。そうしないと、与えられた値が生成されます。
例えば:
foo : ~
bob : (( foo || "default" ))
alice : (( require(foo) || "default" ))
に評価します
foo : ~
bob : ~
alice : default
(( stub(foo.bar) ))
関数stub
それを定義する最初のアップストリームスタブに見られる専用フィールドの値を生成します。
例えば:
Template.yml
value : (( stub(foo.bar) ))
スタブと合併
stub.yml
foo :
bar : foobar
に評価します
value : foobar
この関数に渡された引数は、リテラルまたは参照のパス要素のリストを示す参照を示す文字列のいずれかを評価するリテラルまたは式のいずれかでなければなりません。引数や未定義( ~~
)が与えられていない場合、実際のフィールドパスが使用されます。
特定の単独参照は式として評価されないことに注意してください。その値を使用する必要がある場合は、たとえばリスト式の場合(ref)
または[] ref
示すために式に変換する必要があります。
または、 merge foo.bar
など、 merge
操作を使用できます。違いは、 stub
融合しないため、フィールドは依然としてマージされます(ドキュメント内の元のパスを使用)。
(( tagdef("tag", value) ))
関数tagdef
使用して、動的タグを定義できます(タグを参照)。タグマーカーとは対照的に、この関数は、式によってタグ名とその意図された値を指定できます。したがって、 map
やsum
などの要素を構成する際に使用して、計算値を持つ動的タグを作成できます。
オプションの3番目の引数を使用して、意図したスコープ( local
またはglobal
)を指定できます。デフォルトでは、ローカルタグが作成されます。ローカルタグは実際の処理レベル(テンプレートまたはサブ)でのみ表示されますが、グローバルタグは定義されると、すべてのさらに処理レベル(スタブまたはテンプレート)で使用できます。
または、タグ名には、グローバルタグを宣言するためのstart( *
)でプレフィックスを付けることができます。
指定されたタグ値は、関数の結果として使用されます。
例えば:
Template.yml
value : (( tagdef("tag:alice", 25) ))
alice : (( tag:alice::. ))
に評価します
value : 25
alice : 25
(( eval(foo "." bar ) ))
string式の評価結果を動的式として再度評価します。これは、たとえば、間接を実現するために使用できます。
例:式
alice :
bob : married
foo : alice
bar : bob
status : (( eval( foo "." bar ) ))
フィールドへのパスを計算し、再び評価されて、この構成されたフィールドの値を生成します。
alice :
bob : married
foo : alice
bar : bob
status : married
(( env("HOME" ) ))
名前がダイナミル式として与えられている環境変数の値を読みます。環境変数が設定されていない場合、評価は失敗します。
2番目のフレーバーでは、関数env
複数の引数および/またはリスト引数を受け入れ、単一のリストに結合されます。このリストのすべてのエントリは、環境変数の名前として使用され、関数の結果は、与えられた変数のマップがYAML要素としてです。これにより、存在しない環境変数が省略されます。
(( parse(yamlorjson) ))
YAMLまたはJSON文字列を解析し、YAML値としてコンテンツを返します。したがって、さらなる動的評価に使用できます。
例えば:
json : |
{ "alice": 25 }
result : (( parse( json ).alice ))
フィールドresult
の値25
生成します。
関数parse
オプションの2番目の引数であるParseモードをサポートします。ここでは、読み取り関数と同じモードが可能です。デフォルトの解析モードはimport
であり、コンテンツは単に解析され、このステップではそれ以上の評価はありません。
(( asjson(expr) ))
この関数は、その引数によって与えられたYAML値をJSON文字列に変換します。対応する関数asyaml
YAML値をYAMLドキュメント文字列として生成します。
例えば:
data :
alice : 25
mapped :
json : (( asjson(.data) ))
yaml : (( asyaml(.data) ))
に解決します
data :
alice : 25
mapped :
json : ' {"alice":25} '
yaml : |+
alice: 25
(( catch(expr) ))
この関数は式を実行し、いくつかの評価情報マップを生成します。式が失敗したとしても、常に成功します。マップには次のフィールドが含まれています。
名前 | タイプ | 意味 |
---|---|---|
valid | ブール | 式は有効です |
error | 弦 | 評価のエラーメッセージテキスト |
value | どれでも | 評価が成功した場合、表現の価値 |
例えば:
data :
fail : (( catch(1 / 0) ))
valid : (( catch( 5 * 5) ))
に解決します
data :
fail :
error : division by zero
valid : false
valid :
error : " "
valid : true
value : 25
(( static_ips(0, 1, 3) ))
ジョブの静的IPのリストを生成します。
例えば:
jobs :
- name : myjob
instances : 2
networks :
- name : mynetwork
static_ips : (( static_ips(0, 3, 4) ))
これにより、 mynetwork
のサブネットから3 IPが作成され、2つのインスタンスしかないため、2つのエントリを返します。 2つのエントリは、ネットワークによって定義された静的IP範囲からの0番目と3番目のオフセットです。
たとえば、file bye.ymlを与えられた場合:
networks : (( merge ))
jobs :
- name : myjob
instances : 3
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
hi.ynl :file:
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
返品
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
。
Bye.ymlが代わりにあった場合
networks : (( merge ))
jobs :
- name : myjob
instances : 2
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
spiff merge bye.yml hi.yml
代わりに戻ります
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
、すべてのトランジテクリーに含まれる要素が再びリストまたは整数値のいずれかである限り、リスト引数も受け入れます。これにより、次のようにIPSのリストを省略できます。
static_ips: (( static_ips([1..5]) ))
(( ipset(ranges, 3, 3,4,5,6) ))
歴史的な理由で関数Static_IPSは、Boshマニフェストの構造に依存しており、マニフェストの専用の場所でのみ機能しますが、関数IPSETは純粋に引数に基づいて同様の計算を提供します。したがって、利用可能なIP範囲と必要な数のIPは引数として渡されます。
最初の(範囲)引数は、単純な文字列または文字列のリストとして単一の範囲になります。すべての文字列はそうかもしれません
2番目の引数は、結果セットで要求されたIPアドレスの数を指定します。
追加の引数では、指定された範囲で選択する(0から始まる)IPSのインデックスを指定します。ここでは、インデックスのリストが使用される場合があります。
例えば:
ranges :
- 10.0.0.0 - 10.0.0.255
- 10.0.2.0/24
ipset : (( ipset(ranges,3,[256..260]) ))
ipsetを[ 10.0.2.0, 10.0.2.1, 10.0.2.2 ]
に解決します。
IPインデックスが指定されていない場合(2つの引数のみ)、IPは最初の範囲の先頭から最後の範囲の終わりまで、間接なしに選択されます。
(( list_to_map(list, "key") ))
明示的な名前/キーフィールドを持つマップエントリのリストは、専用キーを使用してマップにマッピングされます。デフォルトでは、キーフィールドname
が使用されます。これは、オプションの2番目の引数によって変更される可能性があります。リスト内の明示的に示されたキーフィールドも考慮されます。
例えば:
list :
- key:foo : alice
age : 24
- foo : bob
age : 30
map : (( list_to_map(list) ))
にマッピングされます
list :
- foo : alice
age : 24
- foo : bob
age : 30
map :
alice :
age : 24
bob :
age : 30
テンプレートとラムダ式と組み合わせて、これを使用して、任意の名前のキー値を持つマップを生成できますが、動的式はキー値では許可されていません。
(( makemap(fieldlist) ))
このフレーバーでは、 makemap
指定されたフィールドリストで説明されているエントリを含むマップを作成します。このリストには、専用のマップエントリを説明するエントリkey
とvalue
のマップが含まれることが期待されています。
例えば:
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map : (( makemap(list) ))
収量
list :
- key : alice
value : 24
- key : bob
value : 25
- key : 5
value : 25
map :
" 5 " : 25
alice : 24
bob : 25
キー値がブール値または整数である場合、文字列にマッピングされます。
(( makemap(key, value) ))
このフレーバーでは、 makemap
指定された引数ペアで説明されているエントリを含むマップを作成します。引数は、キー/値のペアのシーケンスである場合があります(個別の引数で与えられます)。
例えば:
map : (( makemap("peter", 23, "paul", 22) ))
収量
map :
paul : 22
peter : 23
以前のmakemap
フレーバーとは対照的に、これはマップリテラルによっても処理される可能性があります。
(( merge(map1, map2) ))
キーワードのmerge
横には、 merge
と呼ばれる関数もあります(常にオープニングブラケットが続く必要があります)。これは、スタブマージプロセスに類似した実際のドキュメントから撮影されたセベラルマップをマージするために使用できます。マップが参照式で指定されている場合、引数を評価する前に実際のドキュメントのコンテキストで常に評価されるため、動的式を含めることはできません。
例えば:
map1 :
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
result
を解決します
result :
alice : 26
bob : 24 # <---- expression evaluated before mergeing
または、マップテンプレートを渡すことができます(評価オペレーターなし!)。この場合、テンプレートからの動的式が評価されます。
例えば:
map1 :
<< : (( &template ))
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
result
を解決します
result :
alice : 26
bob : 26
マップは、マップ式によっても与えられる場合があります。ここでは、通常の構文を使用してDynaml式を指定することができます。
例えば:
map1 :
alice : 24
bob : 25
map2 :
alice : 26
peter : 8
result : (( merge(map1, map2, { "bob"="(( carl ))", "carl"=100 }) ))
result
を解決します
result :
alice : 26
bob : 100
複数の引数の代わりに、単一のリスト引数を与えることができます。リストには、マージするマップを含める必要があります。
ネストされた合併は、すべての外側のバインディングにアクセスできます。相対的な参照は、実際のドキュメントで最初に検索されます。それらが見つからない場合、すべての外側のバインディングを使用して、内側から外側のバインディングまで参照を検索します。さらに、コンテキスト( __ctx
)にはフィールドOUTER
あります。これは、ネストされた合併のすべての外側のドキュメントのリストであり、絶対参照を検索するために使用できます。
例えば:
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) ))
merged
た解決
merged :
bob : 25
outer1 :
alice :
age : 24
outer2 : 24
sum : 49
(( intersect(list1, list2) ))
関数intersect
複数のリストを交差させます。リストには、あらゆるタイプのエントリが含まれる場合があります。
例えば:
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) ))
とintersect
を解決します
intersect :
- - a
- a
- { a: b }
- 0
- " 0 "
(( reverse(list) ))
関数は、リストの順序を逆転させreverse
。リストには、あらゆるタイプのエントリが含まれている場合があります。
例えば:
list :
- - a
- b
- { a: b }
- { b: c }
- 0
- 1
reverse : (( reverse(list) ))
reverse
に解決します
reverse :
- 1
- 0
- { b: c }
- { a: b }
- b
- - a
(( validate(value,"dnsdomain") ))
関数validate
バリデーターのセットを使用して式を検証します。最初の議論は検証する価値であり、他のすべての引数は、価値を受け入れるために成功しなければならないバリデーターです。少なくとも1つの有効化が失敗した場合、失敗の理由を説明する適切なエラーメッセージが生成されます。
バリデーターは、検証型のタイプを文字列とその引数として含む文字列またはリストで示されます。バリデーターは、先行で否定できます!
その名前で。
次のバリデーターが利用可能です。
タイプ | 議論 | 意味 |
---|---|---|
empty | なし | 空のリスト、マップ、または文字列 |
dnsdomain | なし | DNSドメイン名 |
wildcarddnsdomain | なし | ワイルドカードDNSドメイン名 |
dnslabel | なし | DNSラベル |
dnsname | なし | DNSドメインまたはワイルドカードドメイン |
ip | なし | IPアドレス |
cidr | なし | シドル |
publickey | なし | PEM形式の公開鍵 |
privatekey | なし | PEM形式の秘密鍵 |
certificate | なし | PEM形式の証明書 |
ca | なし | CAの証明書 |
semver | 制約のオプションのリスト | 制約に対してSEMVERバージョンを検証します |
type | 受け入れられているタイプキーのリスト | 少なくとも1つのタイプキーが一致する必要があります |
valueset | 値を含む引数をリストします | 考えられる値 |
value または= | 価値 | 専用値を確認してください |
gt または> | 価値 | (number/string)以上 |
lt または< | 価値 | (number/string)未満 |
ge または>= | 価値 | より大きいまたは等しい(number/string) |
le または<= | 価値 | (number/string)以下 |
match または~= | 正規表現 | 正規表現に一致する文字列値 |
list | エントリバリーターのオプションのリスト | リストとエントリの一致は、有効化されたものです |
map | [[<keybalidator>、] <EntryBalidator>] | 有効化者が与えられたマップとキーとエントリは一致しています |
mapfield | <フィールドネーム> [、<balidator>] | マップへの必要なエントリ |
optionalfield | <フィールドネーム> [、<balidator>] | マップのオプションのエントリ |
and | バリデーターのリスト | すべてのバリデーターは成功する必要があります |
or | バリデーターのリスト | 少なくとも1つのバリデーターが成功する必要があります |
not ではない! | バリデーター | バリデーターの引数を否定する |
検証が成功した場合、値は返されます。
例えば:
dnstarget : (( validate("192.168.42.42", [ "or", "ip", "dnsdomain" ]) ))
に評価します
dnstarget : 192.168.42.42
検証に失敗した場合、障害の理由が生成されるエラーが生成されます。
例えば:
dnstarget : (( validate("alice+bob", [ "or", "ip", "dnsdomain" ]) ))
次のエラーが生成されます。
*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])?)*')])
バリデーターは、少なくとも1つの引数を取り、ブール値を返すラムダの表現である可能性があります。このようにして、YAMLドキュメントの一部として独自のバリデーターを提供することができます。
例えば:
val : (( validate( 0, |x|-> x > 1 ) ))
複数のパラメーターが宣言されている場合、追加の引数をバリデーター引数として指定する必要があります。最初の議論は常にチェックする価値です。
例えば:
val : (( validate( 0, [|x,m|-> x > m, 5] ) ))
Lambda関数は、1、2、または3つの要素でリストを返す場合があります。これは、適切なメッセージを提供するために使用できます。
索引 | 意味 |
---|---|
0 | 最初のインデックスは常に一致結果であり、ブール値として評価できなければなりません |
1 | 2つの要素が与えられた場合、2番目のインデックスは実際の結果を説明するメッセージです |
2 | ここでインデックス1は成功メッセージと2の失敗メッセージを減少させます |
例えば:
val : (( validate( 6, [|x,m|-> [x > m, "is larger than " m, "is less than or equal to " m], 5] ) ))
言うまでもなく、上記の例に示すように、バリデーターの仕様にはインラインで与えられるかもしれませんが、参照式としても与えられます。 not
、 and
or
VALIDATORSは、深くネストされたバリデーターの仕様を受け入れます。
例えば:
dnsrecords :
domain : 1.2.3.4
validator :
- map
- - or # key validator
- dnsdomain
- wildcarddnsdomain
- ip # entry validator
val : (( validate( map, validator) ))
(( check(value,"dnsdomain") ))
関数check
YAML構造をYAMLベースの値チェッカーと一致させるために使用できます。これにより、検証のために既に説明されている同じチェック説明を使用できます。コールの結果は、一致結果を示すブール値です。チェックが失敗しても失敗しません。
(( error("message") ))
関数error
使用して、専用のメッセージで明示的な評価障害を引き起こすことができます。
これは、たとえば、潜在的に失敗するComples Expressionのデフォルトとしてエラー関数を追加することにより、意味のあるメッセージに複雑な処理エラーを減らすために使用できます。
例えば:
value : (( <some complex potentially failing expression> || error("this was an error my friend") ))
別のシナリオは、上流のスタブで定義されるフィールドのエラー式として(デフォルト)値として(デフォルト)値として(デフォルト)値を使用して、必要なフィールドの欠落の記述メッセージを省略することです。
Dynamlはさまざまな数学機能をサポートします:
戻ってくる整数: ceil
、 floor
、 round
、 roundtoeven
戻ってくるフロートまたは整数: abs
帰還フロート: sin
、 cos
、 sinh
、 cosh
、 asin
、 acos
、 asinh
、 acosh
、 sqrt
、 exp
、 log
、 log10
、
Dynamlは、適切な関数によってinteger
、 float
、 bool
、 string
値の間のさまざまなタイプ変換をサポートします。
例えば:
value : (( integer("5") ))
文字列を整数値に変換します。
整数を文字列に変換すると、弦のベースを指定するためのオプションの追加の整数引数を受け入れます。たとえば、 string(55,2)
は"110111"
になります。デフォルトのベースは10です。ベースは2〜36でなければなりません。
Spiffは、テンプレートおよびサブファイル以外のコンテンツへのアクセスをサポートしています。ファイルを読み取り、コマンドとパイプラインを実行することができます。これらの機能はすべて、2つのフレーバーに存在します。
sync
関数が使用されている場合に当てはまります。これは、テンプレート処理を専用の状態(外部コンテンツによって提供)と同期することを目的としています。ここでは、キャッシング操作は役に立たないため、2番目の非獲得フレーバーがあります。すべての関数は、接尾辞_uncached
(たとえばread_uncached()
)で使用できます(( read("file.yml") ))
ファイルを読んでコンテンツを返します。 yaml
ファイル、 text
ファイル、 binary
ファイルの3つのコンテンツタイプのサポートがあります。バイナリモードで読み取ると、Base64エンコードされたマルチライン文字列が得られます。
ファイルの接尾辞が.yml
、 .yaml
または.json
の場合、デフォルトではyamlタイプが使用されます。ファイルをtext
として読み取る必要がある場合、このタイプは明示的に指定する必要があります。他のすべての場合、デフォルトはtext
であるため、バイナリファイル(たとえばアーカイブ)を読み取るには、 binary
モードを指定する必要があります。
オプションの2番目のパラメーターを使用して、目的の返品タイプ: yaml
またはtext
を明示的に指定できます。 YAMLドキュメントの場合、いくつかの追加タイプがサポートされています: multiyaml
、 template
、 templates
、 import
、 importmulti
。
YAMLドキュメントが解析され、ツリーが返されます。ツリーの要素には、通常のDynaml式でアクセスできます。
さらに、YAMLファイルには再びDynaml式が含まれている場合があります。含まれるすべてのDynaml式は、読み取り式のコンテキストで評価されます。これは、YAMLドキュメント内の異なる場所に含まれる同じファイルが、使用されている動的式に応じて、異なるサブツリーをもたらす可能性があることを意味します。
マルチドキュメントYamlを読むことができる場合も。型multiyaml
が指定されている場合、YAMLドキュメントルートノードを備えたリストノードが返されます。
YAMLまたはJSONドキュメントは、タイプtemplate
を指定することにより、テンプレートとして読み取ることもできます。ここで、結果はテンプレート値になり、通常のインラインテンプレートのように使用できます。 templates
が指定されている場合、マルチドキュメントはテンプレートのリストにマッピングされます。
読み取りタイプがimport
ように設定されている場合、ファイルコンテンツはYAMLドキュメントとして読み取り、ルートノードを使用して式を置き換えます。ドキュメントに含まれる潜在的な動的式は、読み取りコールとともに式の実際のバインディングで評価されませんが、元のファイルの一部であったためです。したがって、このモードは、読み取り結果のそれ以上の処理がない場合、または配信された値が未処理の場合にのみ使用できます。
これは、インポートされたドキュメントの専用フラグメントをおかしくするために、チェーンズリファレンス(Examle (( read(...).selection ))
)と一緒に使用できます。次に、評価された部分に対してのみ評価が行われます。他の部分の式と参照は評価されず、まったく行われず、エラーにつながることができません。
例えば:
Template.yaml
ages :
alice : 25
data : (( read("import.yaml", "import").first ))
import.yaml
first :
age : (( ages.alice ))
second :
age : (( ages.bob ))
second
セクションが評価されないため、失敗しません。
このモードは、予期しない結果につながることが多いため、注意して取得する必要があります。
読み取りタイプのimportmulti
使用して、ノードのリストとしてマルチドキュメントYAMLファイルをインポートできます。
テキストドキュメントは単一の文字列として返されます。
また、バイナリドキュメントを読むこともできます。コンテンツは、直接文字列(またはYAMLドキュメント)として使用することはできません。したがって、読み取りモードbinary
指定する必要があります。コンテンツは、base64エンコードされたマルチライン文字列値として返されます。
(( exec("command", arg1, arg2) ))
コマンドを実行します。引数は、リストまたはマップに評価された参照式を含むすべての動的式にすることができます。リストまたはマップは、指定されたフラグメントを含むYAMLドキュメントを含む単一の引数として渡されます。
結果は、コマンドの標準出力を解析することにより決定されます。 YAMLドキュメントまたは単一のマルチライン文字列または整数値かもしれません。 YAMLドキュメントは、ドキュメントプレフィックスから開始する必要があります---
。コマンドが失敗した場合、式は未定義として処理されます。
例えば
arg :
- a
- b
list : (( exec( "echo", arg ) ))
string : (( exec( "echo", arg.[0] ) ))
収量
arg :
- a
- b
list :
- a
- b
string : a
または、 exec
コマンドラインを完全に説明する単一のリスト引数で呼び出すことができます。
複数の式で使用されていても、同じコマンドが1回実行されます。
(( pipe(data, "command", arg1, arg2) ))
コマンドを実行し、標準入力を専用のデータでフィードします。コマンド引数は文字列でなければなりません。コマンドの引数は、リストまたはマップに評価された参照式を含む任意の動的式にすることができます。リストまたはマップは、指定されたフラグメントを含むYAMLドキュメントを含む単一の引数として渡されます。
入力ストリームは、指定されたデータから生成されます。これが単純なタイプの場合、その文字列表現が使用されます。それ以外の場合は、入力データからYAMLドキュメントが生成されます。結果は、コマンドの標準出力を解析することにより決定されます。 YAMLドキュメントまたは単一のマルチライン文字列または整数値かもしれません。 YAMLドキュメントは、ドキュメントプレフィックスから開始する必要があります---
。コマンドが失敗した場合、式は未定義として処理されます。
例えば
data :
- a
- b
list : (( pipe( data, "tr", "a", "z") ))
収量
arg :
- a
- b
list :
- z
- b
または、 pipe
データとコマンドラインを完全に説明するリスト引数で呼び出すことができます。
複数の式で使用されていても、同じコマンドが1回実行されます。
(( write("file.yml", data) ))
ファイルを書き、コンテンツを返します。結果をYAMLドキュメントとして解析できる場合、ドキュメントが返されます。オプションの3番目の引数を使用して、書き込みオプションを渡すことができます。オプションの引数は、ファイル許可を示す整数(デフォルトは0644
)またはオプション付きのコンマ分離文字列です。サポートされているオプションは次のとおりです
binary
:データは、書き込む前にbase64デコードされています0
はオクタル値を示しています。 (( tempfile("file.yml", data) ))
AA一時ファイルを書き込み、そのパス名を返します。オプションの3番目の引数を使用して、書き込みオプションを渡すことができます。それは基本的にwrite
ような動作です
注意:一時的なファイルは、マージ処理中にのみ存在します。その後削除されます。
たとえば、 exec
機能の一時的なファイル引数を提供するために使用できます。
(( lookup_file("file.yml", list) ))
ルックアップファイルはディレクトリのリストです。結果は、既存のファイルのリストです。 lookup_dir
を使用すると、代わりにディレクトリを検索することができます。
既存のファイルが見つからない場合、空のリストが返されます。
複数のリストまたは文字列引数を渡して、検索パスを作成することができます。
(( mkdir("dir", 0755) ))
まだ存在しない場合は、ディレクトリとそのすべての中間ディレクトリを作成します。
許可部分はオプションです(デフォルト0755)。ディレクトリのパスは、値のようなastringによって、またはパスコンポーネントのリストとして与えられる場合があります。
(( list_files(".") ))
ディレクトリ内のファイルを一覧表示します。結果は、既存のファイルのリストです。 list_dirs
使用すると、代わりにディレクトリをリストすることができます。
(( archive(files, "tar") ))
リストされたファイルを含む指定されたタイプ(デフォルトはtar
)のアーカイブを作成します。その結果、base64エンコードアーカイブができました。
サポートされているアーカイブタイプは、 tar
とtargz
です。
files
ファイルエントリのリストまたはマップである場合があります。マップの場合、マップキーはファイルパスのデフォルトとして使用されます。ファイルエントリは、次のフィールドを備えたマップです。
分野 | タイプ | 意味 |
---|---|---|
path | 弦 | マップのファイルパスは、マップキーによってデフォルトであるマップのオプションです |
mode | intまたはint文字列 | ファイルモードまたは書き込みオプション。それは基本的に、 write のオプション引数のように動作します。 |
data | どれでも | ファイルコンテンツ、YAMLはYAMLドキュメントとしてマーシャルされます。 mode バイナリモードを示している場合、文字列値はbase64デコードされます。 |
base64 | 弦 | base64エンコードされたバイナリデータ |
例えば:
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"))) ))
利回り:
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は、セマンティックバージョン名の処理をサポートしています。主要なv
の有無にかかわらず、Masterminds Semverパッケージを受け入れるバージョンからすべての機能をサポートします。
(( semver("v1.2-beta.1") ))
特定の文字列がセマンティックバージョンであるかどうかを確認し、その正規化されたフォームを返します(主要なv
と完全なリリースパーツがメジャー、マイナー、およびパッチバージョン番号が付いています)。
例えば:
normalized : (( semver("v1.2-beta.1") ))
に解決します
normalized : 1.2.0-beta.1
(( semverrelease("v1.2.3-beta.1") ))
メタデータを省略したセマンティックバージョンのリリース部分を返し、情報を繰り返します。
例えば:
release : (( semverrelease("v1.2.3-beta.1") ))
に解決します
release : v1.2.3
追加の文字列引数が与えられた場合、この関数は、メタデータとプレレリーズ情報を保存する指定されたセマンティックバージョンのリリースによってリリースを置き換えます。
例えば:
new : (( semverrelease("1.2.3-beta.1", "1.2.1) ))
に解決します
new : 1.2.1-beta.1
(( semvermajor("1.2.3-beta.1") ))
指定されたセマンティックバージョンのメジャーバージョン番号を決定します。結果は整数です。
例えば:
major : (( semvermajor("1.2.3-beta.1") ))
に解決します
major : 1
関数semverincmajor
使用して、メジャーバージョン番号をインクリメントし、マイナーバージョン、パッチバージョン、リリースサフィックスをリセットできます。
例えば:
new : (( semverincmajor("1.2.3-beta.1") ))
に解決します
new : 2.0.0
(( semverminor("1.2.3-beta.1") ))
指定されたセマンティックバージョンのマイナーバージョン番号を決定します。結果は整数です。
例えば:
minor : (( semverminor("1.2.3-beta.1") ))
に解決します
minor : 2
semverincminor
関数を使用して、マイナーバージョン番号を増やし、パッチバージョンをリセットしてサフィックスをリリースできます。
例えば:
new : (( semverincmajor("v1.2.3-beta.1") ))
に解決します
new : v1.3.0
(( semverpatch("1.2.3-beta.1") ))
指定されたセマンティックバージョンのパッチバージョン番号を決定します。結果は整数です。
例えば:
patch : (( semverpatch("1.2.3-beta.1") ))
に解決します
patch : 3
関数semverincpatch
使用して、パッチバージョン番号をインクリメントするか、リリースサフィックスをリセットできます。 rleaseの接尾辞がある場合、それらは削除され、リリース情報は変更されていません。そうしないと、パッチバージョン番号が増加します。
例えば:
final : (( semverincpatch("1.2.3-beta.1") ))
new : (( semverincpatch(final) ))
に解決します
final : 1.2.3
new : 1.2.4
(( semverprerelease("1.2.3-beta.1") ))
指定されたセマンティックバージョンのプレリリースを決定します。結果は文字列です。
例えば:
prerelease : (( semverprerelease("1.2.3-beta.1") ))
に解決します
prerelease : beta.1
追加の文字列引数が与えられた場合、この関数が設定、交換、またはクリア(空の文字列に設定されている場合)は、プレレリースを
例えば:
new : (( semverprerelease("1.2.3-beta.1", "beta.2) ))
に解決します
new : 1.2.3-beta.2
(( semvermetadata("1.2.3+demo") ))
指定されたセマンティックバージョンのメタデータを決定します。結果は文字列です。
例えば:
metadata : (( semvermetadata("1.2.3+demo") ))
に解決します
metadata : demo
追加の文字列引数が与えられた場合、この関数はメタデータを設定、交換、またはクリアします(空の文字列に設定する場合)メタデータ。
例えば:
new : (( semvermetadata("1.2.3-test", "demo) ))
に解決します
new : 1.2.3+demo
(( semvercmp("1.2.3", 1.2.3-beta.1") ))
2つのセマンティックバージョンを比較します。プレレリーズは、常に最終リリースよりも小さくなります。結果は、次の値を持つ整数です。
結果 | 意味 |
---|---|
-1 | 最初のバージョンは2番目のバージョンの前です |
0 | どちらのバージョンも等しい |
1 | 最初のVersuonは2番目のVersuonの後です |
例えば:
compare : (( semvercmp("1.2.3", "1.2.3-beta.1") ))
に解決します
compare : 1
(( semvermatch("1.2.3", "~1.2") ))
指定されたセマンティックバージョンを、コントレントのリストと一致させます。結果はブール値です。任意の数のバージョンの制約を指定することが可能です。制約が与えられない場合、関数は、指定された文字列がセマンティックバージョンであるかどうかを確認するだけです。
例えば:
match : (( semvermatch("1.2.3", "~1.2") ))
に解決します
match : true
可能な制約仕様の完全なリストは、こちらをご覧ください。
(( semversort("1.2.3", "1.2.1") ))
昇順でバージョンのリストを並べ替えます。主要なv
保存されています。
例えば:
sorted : (( semversort("1.2.3", "1.2.1") ))
に解決します
sorted :
- 1.2.1
- 1.2.3
ソートされるバージョンのリストは、単一のリスト引数で指定される場合があります。
Spiffは、 X509証明書とキーを使用するためのいくつかの有用な機能をサポートしています。また、状態を提供するためのいくつかのヒントを見つけるために、有用なセクションを参照してください。
(( x509genkey(spec) ))
この関数は、プライベートRSAまたはECDSAキーを生成できます。結果は、マルチライン文字列値としてPEMエンコードキーになります。キーサイズ(整数または文字列)が引数として与えられている場合、指定されたキーサイズ(たとえば2048)でRSAキーが生成されます。文字列値の1つが与えられます
この関数は、適切なECDSAキーを生成します。
例えば:
keys :
key : (( x509genkey(2048) ))
のようなものに解決します
key : |+
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwxdZDfzxqz4hlRwTL060pm1J12mkJlXF0VnqpQjpnRTq0rns
CxMxvSfb4crmWg6BRaI1cEN/zmNcT2sO+RZ4jIOZ2Vi8ujqcbzxqyoBQuMNwdb32
...
oqMC9QKBgQDEVP7FDuJEnCpzqddiXTC+8NsC+1+2/fk+ypj2qXMxcNiNG1Az95YE
gRXbnghNU7RUajILoimAHPItqeeskd69oB77gig4bWwrzkijFXv0dOjDhQlmKY6c
pNWsImF7CNhjTP7L27LKk49a+IGutyYLnXmrlarcNYeCQBin1meydA==
-----END RSA PRIVATE KEY-----
(( x509publickey(key) ))
PEM形式の特定のキーまたは証明書(X509Genkey関数で生成された)の場合、この関数は公開キーを抽出し、Multi-Line文字列としてPEM形式で再び返します。
例えば:
keys :
key : (( x509genkey(2048) ))
public : (( x509publickey(key)
のようなものに解決します
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-----
SSH公開キーを生成するには、オプションの追加のフォーマット引数をssh
に設定できます。結果は、SSHに使用可能な通常の公開キー形式になります。デフォルトの形式は、上記のPEM出力形式を提供するpem
です。
RSAキーは、デフォルトではPKCS#1形式( RSA PUBLIC KEY
)でPEMのマーシャルが施されています。汎用PKIX形式( PUBLIC KEY
)が必要な場合は、形式の引数pkix
指定する必要があります。
フォーマットssh
の使用この関数を使用して、PEMフォーマットされた公開キーをSSHキーに変換することもできます。
(( x509cert(spec) ))
関数x509cert
は、自己署名された1つまたは特定のCAによって署名された証明書のいずれかで、ローカルに署名された証明書を作成します。 Multi-Line文字列値としてPEMエンコードされた証明書を返します。
単一の仕様パラメーターは、証明書情報を指定するために使用されるオプションのオプションと非オプションのフィールドを備えたマップを取得します。これは、インラインマップ式またはYAMLドキュメントの残りの部分への任意のマップリファレンスにすることができます。
次のマップフィールドが観察されます。
フィールド名 | タイプ | 必須 | 意味 |
---|---|---|---|
commonName | 弦 | オプション | 主題の一般名フィールド |
organization | 文字列または文字列リスト | オプション | 主題の組織フィールド |
country | 文字列または文字列リスト | オプション | 主題の国のフィールド |
isCA | ブール | オプション | CA証明書のオプション |
usage | 文字列または文字列リスト | 必須 | 証明書の使用キー(以下を参照) |
validity | 整数 | オプション | 妥当性間隔の時間 |
validFrom | 弦 | オプション | 形式の開始時間「1月1日01:22:31 2019」 |
hosts | 文字列または文字列リスト | オプション | DNS名またはIPアドレスのリスト |
privateKey | 弦 | 必須またはpublicKey | 証明書を生成するための秘密鍵 |
publicKey | 弦 | 必須またはprivatekey | 証明書を生成する公開鍵 |
caCert | 弦 | オプション | サインする証明書 |
caPrivateKey | 弦 | オプション | caCert のpriavteキー |
自己署名証明書の場合、 privateKey
フィールドを設定する必要があります。 publicKey
とca
フィールドは省略する必要があります。 caCert
フィールドが与えられた場合、 caKey
フィールドも必要です。 privateKey
フィールドがcaCert
と一緒に与えられている場合、証明書の公開鍵が秘密鍵から抽出されます。
追加のフィールドは静かに無視されます。
次の使用キーがサポートされています(ケースは無視されます):
鍵 | 意味 |
---|---|
Signature | x509.KeyUsageDigitalSignature |
Commitment | x509.KeyUsageContentCommitment |
KeyEncipherment | x509.KeyUsageKeyEncipherment |
DataEncipherment | x509.KeyUsageDataEncipherment |
KeyAgreement | x509.KeyUsageKeyAgreement |
CertSign | X509.KeyUsageCertSign |
CRLSign | x509.KeyUsageCrlSign |
EncipherOnly | x509.KeyUsageEncipheronly |
DecipherOnly | x509.KeyUsagedeCipheronly |
Any | x509.extkeyusageany |
ServerAuth | x509.extkeyusageserverauth |
ClientAuth | x509.extkeyusageclientauth |
codesigning | x509.extkeyuusagecodeing |
EmailProtection | x509.extkeyusageemailprotection |
IPSecEndSystem | x509.extkeyusageipsecendsystem |
IPSecTunnel | x509.extkeyusageipsectunnel |
IPSecUser | x509.extkeyusageipsecuser |
TimeStamping | x509.extkeyusageTimestamping |
OCSPSigning | x509.extkeyusageocspising |
MicrosoftServerGatedCrypto | x509.extkeyuusagemicrosoftservergatedcrypto |
NetscapeServerGatedCrypto | x509.extkeyusagenetscapeservergatedcrypto |
MicrosoftCommercialCodeSigning | x509.extkeyuusagemicrosoftcommercialcodeinging |
MicrosoftKernelCodeSigning | x509.extkeyuusagememicrosoftkernelcodeinging |
例えば:
spec :
<< : (( &local ))
ca :
organization : Mandelsoft
commonName : Uwe Krueger
privateKey : (( data.cakey ))
isCA : true
usage :
- Signature
- KeyEncipherment
data :
cakey : (( x509genkey(2048) ))
cacert : (( x509cert(spec.ca) ))
自己署名のルート証明書を生成し、次のようなものに解決します
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) ))
この関数は、PEM形式で指定された証明書を解析し、フィールドのマップを返します。
フィールド名 | タイプ | 必須 | 意味 |
---|---|---|---|
commonName | 弦 | オプション | 主題の一般名フィールド |
organization | 文字列リスト | オプション | 主題の組織フィールド |
country | 文字列リスト | オプション | 主題の国のフィールド |
isCA | ブール | いつも | CA証明書のオプション |
usage | 文字列リスト | いつも | 証明書の使用キー(以下を参照) |
validity | 整数 | いつも | 妥当性間隔の時間 |
validFrom | 弦 | いつも | 形式の開始時間「1月1日01:22:31 2019」 |
validUntil | 弦 | いつも | 形式の開始時間「1月1日01:22:31 2019」 |
hosts | 文字列リスト | オプション | DNS名またはIPアドレスのリスト |
dnsNames | 文字列リスト | オプション | DNS名のリスト |
ipAddresses | 文字列リスト | オプション | IPアドレスのリスト |
publicKey | 弦 | いつも | 証明書を生成する公開鍵 |
例えば:
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) ))
に解決します
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.
例えば:
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-
例えば:
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.その間
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) ))
収量
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.
例えば:
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.
例えば:
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.
例えば:
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.したがって
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)
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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
.
例えば:
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.
例えば:
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
注意:
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.
例えば:
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.
例えば:
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.
例えば:
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.またはselect
. 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.
例えば
port : 4711
hosts :
- alice
- bob
mapped : (( map[hosts|x|->x ":" port] ))
収量
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.
例えば
list :
- name : alice
age : 25
- name : bob
age : 24
ages : (( map[list|i,p|->i + 1 ". " p.name " is " p.age ] ))
収量
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.
例えば
ages :
alice : 25
bob : 24
keys : (( map[ages|k,v|->k] ))
収量
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
述べる
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[]
例えば
list :
- name : alice
age : 25
- name : bob
age : 26
selected : (( select[list|v|->v.age > 25 ] ))
evaluates selected to
selected :
- name : bob
age : 26
述べる
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 .
述べる
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 関数)。
(( 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.
例えば
list :
- 1
- 2
sum : (( sum[list|0|s,x|->s + x] ))
収量
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.
例えば
list :
- 1
- 2
- 3
prod : (( sum[list|0|s,i,x|->s + i * x ] ))
収量
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.
例えば
ages :
alice : 25
bob : 24
sum : (( map[ages|0|s,k,v|->s + v] ))
収量
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.
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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
例えば:
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
.
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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).)
例えば:
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.
例えば:
template.yaml
data :
<< : (( &tag:persons ))
alice : 25
tagref : (( persons::alice ))
resolves tagref
to 25
(( tag::. ))
Reference the whole (structured) value of tagged node.
例えば:
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.
例えば:
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.
例えば:
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
例えば:
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:
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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:
フィールド名 | タイプ | 意味 |
---|---|---|
VERSION | 弦 | current version of spiff |
FILE | 弦 | name of actually processed template file |
DIR | 弦 | name of directory of actually processed template file |
RESOLVED_FILE | 弦 | name of actually processed template file with resolved symbolic links |
RESOLVED_DIR | 弦 | name of directory of actually processed template file with resolved symbolic links |
PATHNAME | 弦 | 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
.
例えば:
template.yml
foo :
bar :
path : (( __ctx.PATH ))
str : (( __ctx.PATHNAME ))
file : (( __ctx.FILE ))
dir : (( __ctx.DIR ))
evaluates to
例えば:
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.
例えば
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.
例えば:
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.
例えば:
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.
。例えば:
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 (( ~~ ))
.
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
bob :
b1 : 1
b2 : 2
b3 : 3
filtered :
<<for :
key,bob : (( .bob ))
<<mapkey : (( key ))
<<do : (( bob == 2 ? ~~ :bob ))
または
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.
例えば:
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:
例えば:
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
返品
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
と
stub.yml
foo :
alice : 24
bob : 26
収量
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"] ))
と
stub.yml
foo :
- peter
- paul
収量
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 ))
と
stub.yml
people :
- alice : 13
収量
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
収量
meta :
properties :
alice : 24
bob : 42
Functions and mappings can freely be nested
例えば:
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.
例えば:
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.
例えば:
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.
例えば:
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
と
stub.yml
alice : (( config.alice * 2 || ~ ))
bob : (( config.bob * 3 || ~~ ))
収量
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
と
mapping.yml
config :
alice : (( ~~ ))
bob : (( ~~ ))
alice : (( config.alice || ~~ ))
bob : (( config.bob || ~~ ))
peter : (( config.peter || ~~ ))
そして
config.yml
config :
alice : 4711
peter : 0815
収量
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.
例えば:
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.
例えば:
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 ))
そして
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.
例えば:
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):
タグ | 意味 |
---|---|
* | 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