_ __ __
___ _ __ (_)/ _|/ _| _ _
/ __| '_ | | |_| |_ _| |_ _| |_
__ |_) | | _| _|_ _|_ _|
|___/ .__/|_|_| |_| |_| |_|
|_|
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 之外,還有一個 golang 庫,可以在任何 GO 程式(例如 Landscaper)中使用 spiff 模板處理。
模板引擎提供對基於可配置虛擬檔案系統或進程系統的檔案系統的訪問,以執行命令並將輸出合併到範本處理中。
內容:
<<if:
<<switch:
<<type:
<<for:
<<merge:
可以透過 Darwin、Linux 和 PowerPC 電腦(以及虛擬機器)的 Github 版本下載官方版本的可執行二進位。
自上次正式發布以來,spiff 的一些依賴項已發生更改,並且 spiff 將不會更新以跟上這些依賴項。這些依賴項要么是固定的,要么是複製到本地程式碼庫中的。
spiff merge template.yml [template2.yml ...]
將一堆範本文件合併到一個清單中,並將其列印出來。
有關範本文件的詳細信息,請參閱“動態範本語言”,有關更複雜的範例,請參閱範例/子目錄。
例子:
spiff merge cf-release/templates/cf-deployment.yml my-cloud-stub.yml
可以使用檔案名稱-
來從標準輸入讀取一個檔案。它只能使用一次。這允許使用 spiff 作為管道的一部分來僅處理單一流或基於多個模板/存根處理流。
範本文件(第一個參數)可以是包含多個 YAML 文件的多重文件流,這些文件由僅包含---
行分隔。每個 YAML 文件將使用給定的存根檔案獨立處理。結果是按相同順序處理的文檔流。如果文件的根節點被標記為臨時,則該文件將從輸出流中省略。例如,這可用於產生kubectl
使用的kubernetes清單。
merge
命令提供了幾個選項:
選項--partial
。如果指定此選項,spiff 會處理不完整的表達式求值。所有錯誤都將被忽略,並且 yaml 文件中無法解析的部分將作為字串傳回。
使用選項--json
輸出將採用 JSON 格式而非 YAML。
選項--path <path>
可用於輸出巢狀路徑,而不是完整的處理文件。
如果輸出是列表,選項--split
將每個列表元素輸出為單獨的文檔。 yaml格式照常使用---
作為分隔線。 json格式輸出一系列json文檔,每行一個。
使用--select <field path>
可以選擇已處理文件的專用欄位用於輸出
使用--evaluate <dynaml expression>
可以在已處理文件上評估給定的動態表達式以取得輸出。在應用選擇路徑之前對表達式進行求值,然後選擇路徑將對求值結果起作用。
選項--state <path>
啟用spiff的狀態支援。如果給定檔案存在,則將其放置在已配置存根清單的頂部,如果給定檔案存在,則將其放置在已配置存根清單的頂部以進行合併處理。除了已處理文件的輸出之外,還會過濾帶有&state
標記的節點。然後,此過濾後的文件儲存在指定文件下,以.bak
後綴保存舊狀態文件。這可以與狀態實用程式庫提供的手動合併一起使用。
使用選項--bindings <path>
可以指定 yaml 文件,其內容用於建構用於處理的附加綁定。 yaml 文件必須包含一個映射。每個鍵都用作附加綁定。不處理綁定文檔,值依定義使用。
使用選項--tag <tag>:<path>
可以指定 yaml 文件,其內容用作預先定義全域標記的值(請參閱標記)。可以透過<tag>::<ref>
形式的引用表達式來存取標籤。與綁定相比,標記內容不與文件中的節點競爭,它使用另一個引用名稱空間。
使用選項--define <key>=<value>
(簡寫-D
)可以在命令列上指定附加綁定值,覆寫綁定檔案中的綁定值。該選項可能會出現多次。
如果鍵包含點( .
),它將被解釋為路徑表達式來描述深度映射值中的欄位。點(以及點之前的 )可以透過
進行轉義,以將其保留在欄位名稱中。
選項--preserve-escapes
將保留動態表達式和清單/映射合併指令的轉義。如果打算使用spiff對處理結果進行進一步的處理步驟,則可以使用此選項。
選項--preserve-temporary
將保留最終文件中標記為暫時的欄位。
選項--features=<featurelist>
將啟用此給定功能。必須明確啟用與舊行為不相容的新功能。通常,這些功能不會破壞常見行為,而是為先前用作常規值的 yaml 值引入專用解釋。
資料夾庫提供了一些有用的實用程式庫。它們也可以用作此模板引擎強大功能的範例。
spiff diff manifest.yml other-manifest.yml
顯示兩個部署清單之間的結構差異。這裡還支援具有多個文件的流。為了表明沒有差異,兩個流中的文檔數量必須相同,並且第一個流中的每個文檔與第二個流中具有相同索引的文檔相比必須沒有差異。發現的差異會分別顯示每個文件。
與基本比較工具甚至bosh diff
不同,此命令具有部署清單的語義知識,而不僅僅是基於文字。例如,如果兩個清單是相同的,只是它們有一些以不同順序列出的作業, spiff diff
將檢測到這一點,因為作業順序在清單中很重要。另一方面,例如,如果兩個清單僅在其資源池的順序上有所不同,那麼它將產生並清空 diff,因為資源池順序實際上對於部署並不重要。
與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 或僅規範欄位的順序。可用選項為--json
、 --path
、 --split
或--select
根據其對於merge
子指令的意義。
spiff encrypt secret.yaml
encrypt
子命令可用於根據encrypt
動態函數對資料進行加密或解密。密碼可以作為第二個參數給出,也可以從環境變數SPIFF_ENCRYPTION_KEY
中取得。最後一個參數可用來傳遞加密方法(請參閱encrypt
函數)
數據取自指定文件。如果給定-
,則從 stdin 讀取。
如果給予選項-d
,則資料被解密,否則資料被讀取為yaml文件並列印加密結果。
必須明確啟用與舊行為不相容的新功能。通常,這些功能不會破壞常見行為,但會為先前用作常規值的 yaml 值引入專用解釋,因此可能會破壞現有用例。
目前支援以下功能標誌:
特徵 | 自從 | 狀態 | 意義 |
---|---|---|---|
interpolation | 1.7.0-beta-1 | 阿爾法 | Dynaml 作為 yaml 字串的一部分 |
control | 1.7.0-beta-4 | 阿爾法 | 基於 yaml 的控制結構 |
可以使用動態函數features()
作為字串清單來查詢活動功能標誌。如果使用字串參數呼叫此函數,它將傳回給定的功能目前是否啟用。
可以透過命令列使用--features
選項、透過 go 庫使用WithFeatures
函數或通常透過將環境變數SPIFF_FEATURES
設定為功能列表來啟用功能。此設定始終用作預設設定。透過使用 go 庫中的Plain()
spiff 設置,所有環境變數都將被忽略。
功能可以透過名稱指定,也可以透過名稱前面加上前綴no
來停用它。
庫資料夾包含一些有用的spiff範本庫。這些基本上只是添加到合併文件清單中的存根,以提供合併處理的實用功能。
Spiff 使用一種聲明性的、無邏輯的模板語言,稱為「dynaml」(動態 yaml)。
每個動態節點保證解析為 YAML 節點。它不是字串插值。這使得開發人員不必考慮如何在產生的範本中呈現值。
動態節點以字串形式出現在 .yml 檔案中,表示由兩個括號括起來的表達式(( <dynaml> ))
。它們可以用作映射的值或清單中的條目。該表達式可能跨越多行。在任何情況下,yaml 字串值都不能以換行符號結尾(例如使用|-
)
如果括號內的值不應被解釋為動態表達式並按原樣保留在輸出中,則可以在左括號後直接使用感嘆號對其進行轉義。
例如, ((! .field ))
對應到字串值(( .field ))
, ((!! .field ))
對應到字串值((! .field ))
。
以下是動態表達式的完整清單:
(( 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
。
路徑是由點分隔的一系列步驟。步驟可以是用於映射的單詞,也可以是用於列表索引的括號括起來的數字。索引可能為負數(減號後跟數字)。負索引從列表末尾獲取(有效索引=索引+長度(列表))。
無法解析的路徑會導致評估錯誤。如果預計有時不會提供引用,則應與“||”結合使用(見下文)以確保分辨率。
注意:現在,動態語法器已重新設計以啟用常用的索引語法。而不是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.alice.age
來引用,而不是list[0].age
。
預設情況下,名稱為name
的欄位用作關鍵欄位。如果應將另一個字段用作關鍵字段,則可以透過在字段名稱前添加關鍵字key:
來將其在一個清單條目中標記為關鍵。該關鍵字將被處理刪除,並且不會成為最終處理結果的一部分。
例如:
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。
索引可以是整數常數(不含空格),如上一節所述。但它也可能是任意動態表達式(甚至是整數,但帶有空格)。如果表達式的計算結果為字串,它將尋找專用欄位。如果表達式的計算結果為整數,則尋址具有該索引的陣列元素。索引運算子前面的點 ( .
) 是可選的。
例如:
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提取長度為end-start+1的列表,其中元素從索引start到end 。如果起始索引為負,則從清單末端從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 } ))
地圖文字可用於將地圖描述為動態表達式的一部分。鍵和值都可能再次是表達式,其中鍵表達式的計算結果必須為字串。這樣就可以使用非靜態鍵來建立映射。選擇賦值運算子=
而不是 yaml 中使用的常規冒號:
字符,因為這會導致與 yaml 語法衝突。
映射文字可能包含任意數量的由逗號,
分隔的欄位分配。
例如:
name : peter
age : 23
map : (( { "alice" = {}, name = age } ))
產量
name : peter
age : 23
map :
alice : {}
peter : 23
基於表達式組成列表的另一種方法是函數makemap
和list_to_map
。
(( ( "alice" = 25 ) alice ))
任何表達式都可以由任意數量的明確作用域文字開頭。作用域文字描述了一個映射,其值可用於表達式的相對引用解析(靜態作用域)。它為給定名稱創建額外的本地綁定。
作用域文字可能由任意數量的由逗號,
分隔的欄位分配組成。鍵和值均由表達式給出,而鍵表達式的計算結果必須為字串。所有表達式都在下一個外部作用域中求值,這表示作用域中的後續設定不能使用同一作用域文字中的早期設定。
例如:
scoped : (( ( "alice" = 25, "bob" = 26 ) alice + bob ))
產量
scoped : 51
欄位名稱也可以用符號 ( $
name ) 表示。
(( foo bar ))
連接表達式用於連接動態表達式序列。
(( "foo" bar ))
連接(其中 bar 是另一個動態表達式)。任何簡單值(字串、整數和布林值)序列都可以連接,由任何動態表達式給出。
例如:
domain : example.com
uri : (( "https://" domain ))
在此範例中uri
將解析為值"https://example.com"
。
(( [1,2] bar ))
將列表串聯為表達式(其中 bar 是另一個動態表達式)。任何列表序列都可以連接,由任何動態表達式給出。
例如:
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 ]
。
如果第二個表達式的計算結果不是列表(整數、布林值、字串或映射),則該值將附加到第一個列表。
例如:
foo : 3
bar : (( [1] 2 foo "alice" ))
產生bar
的列表[ 1, 2, 3, "alice" ]
。
(( map1 map2 ))
將映射串聯起來作為表達式。任何映射序列都可以連接,由任何動態表達式給出。從而條目將被合併。具有相同鍵的條目將從左到右被覆蓋。
例如:
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 ))
將嘗試從第一個存根或第二個存根等引入foo.bar.baz
,並從提供它的最後一個存根傳回值。
如果對應的值沒有定義,則傳回nil。這與引用表達式具有相同的語義;零合併是一個未解決的模板。參見||
。
<<: (( merge ))
將映射或清單與某些存根中找到的相同元素的內容合併。
** 注意 ** 這種形式的merge
有相容性問題。在 1.0.8 之前的版本中,該表達式從未被解析,只有鍵<<:
的存在與否相關。因此,常使用<<: (( merge ))
其中<<: (( merge || nil ))
的意思。第一個變體需要至少一個存根中的內容(與合併運算子一樣)。現在,該表達式已正確計算,但這會破壞現有的清單範本集,該範本集使用第一個變體,但意味著第二個變體。因此,這種情況被明確處理以描述可選合併。如果確實需要合併意味著額外的顯式限定符必須
注意:現在可以使用 <<<: 而不是使用<<<:
<<:
插入欄位來放置合併表達式,這也允許對類似 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
如果不需要插入新條目(按照插入合併表達式的要求),而只想覆蓋現有條目,則可以在一個現有鍵字段前面添加標籤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 清單中。另一種可能性是使用單一串聯表達式。
<<: (( 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 ))
動態表達式可用於執行整數和浮點算術計算。支援的操作有+
、 -
、 *
和/
。模運算子 ( %
) 僅支援整數運算元。
例如:
值.yml
foo : 3
bar : (( 1 + 2 * foo ))
spiff merge values.yml
為bar
生成7
。這可以與串聯結合使用(計算的優先權高於動態表達式中的串聯):
foo : 3
bar : (( foo " times 2 yields " 2 * foo ))
結果是字串3 times 2 yields 6
。
(( "10.10.10.10" - 11 ))
除了整數算術之外,還可以對 ip 位址和 cidrs 使用加法和減法。
例如:
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
減法也適用於兩個 IP 位址或 cidr,以計算兩個 IP 位址之間的 IP 位址數量。
例如:
diff : (( 10.0.1.0 - 10.0.0.1 + 1 ))
產生值 256。如果操作需要,它們會隱式轉換為字串並傳回 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 支援比較運算子<
、 <=
、 ==
、 !=
、 >=
、 >
。比較運算子適用於整數值。相等性檢查也適用於清單和地圖。結果始終是布林值。要否定某個條件,可以使用一元非運算子 ( !
)。
此外,還有三元條件運算子?:
,可用於根據條件計算表達式。第一個操作數用作條件。如果條件為真,則表達式計算為第二個操作數,否則計算為第三個運算元。
例如:
foo : alice
bar : bob
age : 24
name : (( age > 24 ? foo :bar ))
產生屬性name
的值bob
。
如果一個表達式的計算結果為假,則該表達式被認為是false
false
否則視為true
評論
如果完整的表達式不是帶引號的字串值,則使用符號:
可能會與 yaml 語法發生衝突。
運算子-or
和-and
可用於組合比較運算子以組成更複雜的條件。
評論:
更傳統的運算符符號||
(和&&
) 不能在這裡使用,因為運算子||
已經存在於具有不同語義的 dynaml 中,該語義不適用於邏輯操作。表達式false || true
計算結果為false
,因為如果定義了第一個操作數,則無論其值為何,它都會產生第一個操作數。為了盡可能相容,不能更改此名稱,並且不能使用裸符號or
和and
,因為這將使具有此類名稱的引用的串聯無效。
(( 5 -or 6 ))
如果-or
或-and
運算子兩邊的計算結果均為整數值,則執行位元運算,結果再次為整數。因此表達式5 -or 6
計算結果為7
。
Dynaml 支援一組預定義函數。函數通常被稱為
result : (( functionname(arg, arg, ...) ))
可以使用 lambda 表達式將其他函數定義為 yaml 文件的一部分。函數名稱可以是分組表達式,也可以是託管 lambda 表達式的節點的路徑。
(( format( "%s %d", alice, 25) ))
根據動態表達式給出的參數格式化字串。此函數還有第二種風格: error
格式化錯誤訊息並將評估設為失敗。
(( join( ", ", list) ))
使用給定的分隔符號字串將清單條目或直接值連接到單一字串值。 join 的參數可以是對列表求值的動態表達式,其值同樣是字串或整數,或字串或整數值。
例如:
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 "
可以指定可選的第三個參數。它限制傳回清單條目的數量。值 -1 導致列表長度不受限制。
如果需要使用正規表示式作為分隔符號字串,可以使用split_match
函數。
(( trim(string) ))
修剪字串或字串清單的所有元素。有一個可選的第二個字串參數。它可用於指定將被剪切的一組字元。預設切割集由空格和製表符組成。
例如:
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
如果需要對其他類型進行排序,特別是像清單或映射這樣的複雜類型,或者需要不同的比較規則,則可以將比較函數指定為可選的第二個參數。比較函數必須是帶有兩個參數的 lambda 表達式。結果類型必須是integer
或bool
指示a是否小於b 。如果傳回一個整數,它應該是
例如:
list :
- alice
- foobar
- bob
sorted : (( sort(list, |a,b|->length(a) < length(b)) ))
sorted
的產量
- bob
- alice
- foobar
(( replace(string, "foo", "bar") ))
將字串中所有出現的子字串替換為替換字串。使用可選的第四個整數參數,可以限制替換次數(-1 表示無限)。
例如:
string : (( replace("foobar", "o", "u") ))
產生fuubar
。
如果應使用正規表示式作為搜尋字串,則可以使用函數replace_match
。這裡搜尋字串被評估為正規表示式。它可能包含子表達式。這些匹配可以在替換字串中使用
例如:
string : (( replace_match("foobar", "(o*)b", "b${1}") ))
產生fbooar
。
替換參數也可能是 lambda 函數。在這種情況下,對於每個匹配,都會呼叫該函數來確定替換值。單一輸入參數是實際子表達式匹配的列表。
例如:
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
可以給出整數類型的第三個參數來請求最多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
可選的第二個參數可用於指定最大行長度。在這種情況下,結果將是多行字串。
(( hash(string) ))
函數hash
為給定的字串產生多種雜湊值。預設產生sha256
哈希。可選的第二個參數指定哈希類型。可能的類型有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
使用指定的成本因子(如果缺失,則預設為 10)為給定字串產生 bcrypt 密碼雜湊。
例如:
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 檔案中。處理後的結果將包含解密的值。所有節點類型都可以加密和解密,包括完整的映射和清單。
解密的密碼可以作為第二個參數給出,或者(首選方式)可以透過環境變數SPIFF_ENCRYPTION_KEY
指定。
可選的最後一個參數可以選擇加密方法。迄今為止唯一支援的方法是3DES
。透過使用 spiff 庫提供的加密方法註冊,可以為專用 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為 ( n ,0] 範圍內的整數值 |
布林值 | 布林值 |
細繩 | 一個符文字串,其中符文位於給定字元範圍內,可以使用可用於正規表示式的字元類別或字元範圍的任何組合。如果指定了附加長度參數,則結果字串將具有給定的長度。 |
例如:
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) ))
函數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
產生在定義它的第一個上游存根中找到的專用欄位的值。
例如:
模板.yml
value : (( stub(foo.bar) ))
與存根合併
存根.yml
foo :
bar : foobar
評估為
value : foobar
傳遞給此函數的參數必須是引用文字或計算結果為表示引用的字串或表示引用的路徑元素清單的字串清單的表達式。如果未給予參數或未定義 ( ~~
),則使用實際的欄位路徑。
請注意,給定的唯一參考不會被計算為表達式,如果應使用其值,則必須將其轉換為表達式,例如透過表示列表表達式的(ref)
或[] ref
。
或可以使用merge
操作,例如merge foo.bar
。不同之處在於stub
不會合併,因此該欄位仍將被合併(與文件中的原始路徑)。
(( tagdef("tag", value) ))
函數tagdef
可用於定義動態標籤(請參閱標籤)。與標籤標記相反,此函數允許透過表達式指定標籤名稱及其預期值。因此,它可以用於組合諸如map
或sum
之類的元素,以創建具有計算值的動態標籤。
可選的第三個參數可用於指定預期範圍( local
或global
)。預設會建立本地標記。局部標籤僅在實際處理層級(範本或子)中可見,而全域標籤一旦定義,就可以在所有進一步的處理層級(存根或範本)中使用。
或者,標記名稱可以以開始 ( *
) 為前綴來宣告全域標記。
指定的標記值將用作函數的結果。
例如:
模板.yml
value : (( tagdef("tag:alice", 25) ))
alice : (( tag:alice::. ))
評估為
value : 25
alice : 25
(( eval(foo "." bar ) ))
將字串表達式的求值結果再次作為動態表達式求值。例如,這可以用於實現間接。
例如: 中的表達式
alice :
bob : married
foo : alice
bar : bob
status : (( eval( foo "." bar ) ))
計算欄位的路徑,然後再次計算該路徑以產生該組合欄位的值:
alice :
bob : married
foo : alice
bar : bob
status : married
(( env("HOME" ) ))
讀取名稱以動態表達式形式給出的環境變數的值。如果未設定環境變量,則評估失敗。
在第二種風格中,函數env
接受多個參數和/或列表參數,這些參數連接到一個列表。此清單中的每個條目都用作環境變數的名稱,函數的結果是給定變數作為 yaml 元素的對應。因此省略不存在的環境變數。
(( parse(yamlorjson) ))
解析 yaml 或 json 字串並將內容作為 yaml 值傳回。因此它可以用於進一步的動態評估。
例如:
json : |
{ "alice": 25 }
result : (( parse( json ).alice ))
為字段result
產生值25
。
函數parse
支援可選的第二個參數,即parse mode 。這裡可以使用與讀取功能相同的模式。預設的解析方式是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,並傳回兩個條目,因為只有兩個執行個體。這兩個條目將是網路定義的靜態 IP 範圍的第 0 個和第 3 個偏移量。
例如,給定檔案bye.yml :
networks : (( merge ))
jobs :
- name : myjob
instances : 3
networks :
- name : cf1
static_ips : (( static_ips(0,3,60) ))
並文件hi.yml :
networks :
- name : cf1
subnets :
- cloud_properties :
security_groups :
- cf-0-vpc-c461c7a1
subnet : subnet-e845bab1
dns :
- 10.60.3.2
gateway : 10.60.3.1
name : default_unused
range : 10.60.3.0/24
reserved :
- 10.60.3.2 - 10.60.3.9
static :
- 10.60.3.10 - 10.60.3.70
type : manual
spiff merge bye.yml hi.yml
回報
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
也接受列表參數,只要所有可傳遞包含的元素又是列表或整數值。這允許縮寫 IP 列表,如下所示:
static_ips: (( static_ips([1..5]) ))
(( ipset(ranges, 3, 3,4,5,6) ))
雖然函數 static_ips 由於歷史原因依賴 bosh 清單的結構並且僅在清單中的專用位置工作,但函數ipset純粹基於其參數提供類似的計算。因此,可用的 IP 範圍和所需的 IP 數量作為參數傳遞。
第一個(範圍)參數可以是單一範圍,如簡單字串或字串清單。每個字串都可能是
第二個參數指定結果集中請求的 IP 位址數。
附加參數指定在給定範圍內要選擇的 IP 索引(從 0 開始)。這裡再次可以使用索引列表。
例如:
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 索引(僅兩個參數),則從第一個範圍的開頭到最後一個給定範圍的結尾選擇 IP,無間接選擇。
(( list_to_map(list, "key") ))
具有明確名稱/鍵欄位的對應條目清單將會對應到具有專用鍵的對應。預設使用鍵字段name
,可以透過可選的第二個參數進行更改。清單中明確指出的關鍵欄位也將被考慮在內。
例如:
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
與模板和 lambda 表達式結合使用,可以使用任意命名的鍵值產生映射,但鍵值不允許使用動態表達式。
(( 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
或可以傳遞地圖模板(無需評估運算符!)。在這種情況下,在合併給定文件時評估範本中的動態表達式,就像spiff merge的常規呼叫一樣。
例如:
map1 :
<< : (( &template ))
alice : 24
bob : (( alice ))
map2 :
alice : 26
peter : 8
result : (( merge(map1,map2) ))
將result
解析為
result :
alice : 26
bob : 26
映射也可以由映射表達式給出。這裡可以使用常用語法指定動態表達式:
例如:
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
使用一組驗證器來驗證表達式。第一個參數是要驗證的值,所有其他參數都是必須成功接受該值的驗證器。如果至少一個驗證器失敗,則會產生適當的錯誤訊息來解釋失敗原因。
驗證器由字串或包含驗證器類型及其參數的清單表示。驗證器可以通過前面的!
以它的名義。
以下驗證器可用:
類型 | 論據 | 意義 |
---|---|---|
empty | 沒有任何 | 空列表、地圖或字串 |
dnsdomain | 沒有任何 | DNS網域名稱 |
wildcarddnsdomain | 沒有任何 | 通配符 dns 域名 |
dnslabel | 沒有任何 | 網域解析標籤 |
dnsname | 沒有任何 | DNS 網域或通配符域 |
ip | 沒有任何 | IP位址 |
cidr | 沒有任何 | 西德 |
publickey | 沒有任何 | pem 格式的公鑰 |
privatekey | 沒有任何 | pem 格式的私鑰 |
certificate | 沒有任何 | pem 格式的證書 |
ca | 沒有任何 | CA憑證 |
semver | 可選的約束列表 | 根據約束驗證 semver 版本 |
type | 可接受的類型鍵列表 | 至少有一個類型鍵必須匹配 |
valueset | 列出帶有值的參數 | 可能的值 |
value 或= | 價值 | 檢查專用值 |
gt 或> | 價值 | 大於(數字/字串) |
< lt | 價值 | 小於(數字/字串) |
ge 或>= | 價值 | 大於或等於(數字/字串) |
le 或<= | 價值 | 小於或等於(數字/字串) |
match 或~= | 正規表示式 | 符合正規表示式的字串值 |
list | 可選的條目驗證器列表 | 清單和條目是否與給定的驗證器匹配 |
map | [[ <金鑰驗證器>, ] <條目驗證器> ] | 映射、鍵和條目是否與給定的驗證器匹配 |
mapfield | <欄位名稱> [ , <驗證器>] | 地圖中的必填項 |
optionalfield | <欄位名稱> [ , <驗證器>] | 地圖中的可選條目 |
and | 驗證者列表 | 所有驗證者都必須成功 |
or | 驗證者列表 | 至少有一個驗證者必須成功 |
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])?)*')])
驗證器也可能是至少帶有一個參數並傳回一個布林值的 lambda 表達式。這樣就可以提供自己的驗證器作為 yaml 文件的一部分。
例如:
val : (( validate( 0, |x|-> x > 1 ) ))
如果聲明了多個參數,則必須將附加參數指定為驗證器參數。第一個參數始終是要檢查的值。
例如:
val : (( validate( 0, [|x,m|-> x > m, 5] ) ))
lambda 函數也可以傳回包含 1、2 或 3 個元素的清單。這可用於提供適當的訊息。
指數 | 意義 |
---|---|
0 | 第一個索引始終是匹配結果,它必須可評估為布林值 |
1 | 如果給出兩個元素,第二個索引是描述實際結果的訊息 |
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
驗證器接受深度嵌套的驗證器規格。
例如:
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
可用於透過專用訊息導致明確評估失敗。
例如,這可用於透過附加錯誤函數作為可能失敗的複雜表達式的預設值,將複雜處理錯誤減少為有意義的訊息。
例如:
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。
Spiff支援存取範本和子文件之外的內容。可以讀取檔案、執行命令和管道。所有這些功能都以兩種形式存在。
sync
功能,就會出現這種情況,該功能旨在將範本處理與專用狀態(由外部內容提供)同步。在這裡,快取操作沒有用,因此存在第二種未快取的風格。每個函數都帶有後綴_uncached
(例如read_uncached()
) (( read("file.yml") ))
讀取文件並返回其內容。支援三種內容類型: yaml
檔案、 text
檔案和binary
檔案。以二進位模式讀取將產生 Base64 編碼的多行字串。
如果檔案後綴為.yml
、 .yaml
或.json
,則預設使用 yaml 類型。如果檔案應讀取為text
,則必須明確指定此類型。在所有其他情況下,預設值為text
,因此讀取二進位檔案(例如存檔)迫切需要指定binary
模式。
可選的第二個參數可用於明確指定所需的回傳類型: yaml
或text
。對於yaml文檔,支援一些附加類型: multiyaml
、 template
、 templates
、 import
和importmulti
。
將解析 yaml 文件並傳回樹。樹的元素可以透過正規動態表達式存取。
此外,yaml 檔案可能再次包含動態表達式。所有包含的動態表達式都將在讀取表達式的上下文中進行評估。這意味著 yaml 文件中不同位置包含的相同文件可能會產生不同的子樹,這取決於所使用的動態表達式。
如果也可以讀取多個文檔 yaml。如果給予類型multiyaml
,則傳回帶有 yaml 文件根節點的清單節點。
yaml 或 json 文件也可以透過指定類型template
來讀取為模板。這裡的結果將是一個模板值,可以像常規內聯模板一樣使用。如果指定了templates
,則多文件將對應到模板清單。
如果讀取類型設定為import
,則將文件內容讀取為 yaml 文檔,並使用根節點取代表達式。文件中包含的潛在動態表達式不會透過表達式與讀取呼叫的實際綁定來評估,而是因為它是原始文件的一部分。因此,只有在沒有進一步處理讀取結果或未處理傳遞的值時才能使用此模式。
這可以與連結引用(例如(( read(...).selection ))
)一起使用來選擇匯入文件的專用片段。然後,將僅對所選部分進行評估。其他部分中的表達式和引用根本不會被評估,並且不會導致錯誤。
例如:
模板.yaml
ages :
alice : 25
data : (( read("import.yaml", "import").first ))
導入.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
。
即使在多個表達式中使用相同的指令,也只會執行一次。
(( 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
。
即使在多個表達式中使用相同的指令,也只會執行一次。
(( write("file.yml", data) ))
寫入文件並返回其內容。如果結果可以解析為yaml文檔,則傳回該文檔。可選的第三個參數可用於傳遞寫入選項。選項參數可能是表示檔案權限的整數(預設為0644
)或帶有選項的逗號分隔字串。支援的選項有
binary
:資料在寫入前經過 Base64 解碼0
表示八進位值。 (( tempfile("file.yml", data) ))
寫入臨時檔案並返回其路徑名。可選的第三個參數可用於傳遞寫入選項。它的行為基本上就像write
注意:臨時文件僅在合併處理期間存在。之後它將被刪除。
例如,它可用於為exec
函數提供臨時檔案參數。
(( lookup_file("file.yml", list) ))
查找檔案是目錄列表。結果是現有文件的清單。使用lookup_dir
可以改為查找目錄。
如果找不到現有文件,則傳回空列表。
可以傳遞多個清單或字串參數來組成搜尋路徑。
(( mkdir("dir", 0755) ))
建立一個目錄及其所有中間目錄(如果它們尚不存在)。
權限部分是可選的(預設0755)。目錄的路徑可以由類似的值或路徑組件清單給出。
(( 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支援語意版本名稱的處理。它支援 Masterminds Semver 套件的所有功能,接受有或沒有前導v
的版本。
(( 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") ))
比較兩個語意版本。預發行版總是小於最終發行版。結果是一個具有以下值的整數:
結果 | 意義 |
---|---|
-1 | 第一個版本在第二個版本之前 |
0 | 兩個版本是相同的 |
1 | 第一個版本在第二個版本之後 |
例如:
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 金鑰。給定字串值之一
此函數將產生適當的 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 函數產生),此函數會提取公鑰並以 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
建立本地簽署的證書,可以是自簽名的證書,也可以是由給定 ca 簽署的證書。它將 PEM 編碼的憑證作為多行字串值傳回。
單一規範參數採用帶有一些可選和非可選字段的映射,用於指定證書資訊。它可以是內聯映射表達式或對 yaml 文件其餘部分的任何映射引用。
觀察到以下地圖字段:
欄位名稱 | 類型 | 必需的 | 意義 |
---|---|---|---|
commonName | 細繩 | 選修的 | 主題的通用名稱字段 |
organization | 字串或字串列表 | 選修的 | 主題組織領域 |
country | 字串或字串列表 | 選修的 | 主題國家領域 |
isCA | 布林值 | 選修的 | 憑證的CA選項 |
usage | 字串或字串列表 | 必需的 | 憑證的使用金鑰(見下文) |
validity | 整數 | 選修的 | 有效期間隔(小時) |
validFrom | 細繩 | 選修的 | 開始時間,格式為“Jan 1 01:22:31 2019” |
hosts | 字串或字串列表 | 選修的 | DNS 名稱或 IP 位址列表 |
privateKey | 細繩 | 必需或公鑰 | 用於產生憑證的私鑰 |
publicKey | 細繩 | 必需或私鑰 | 用於產生憑證的公鑰 |
caCert | 細繩 | 選修的 | 簽署用的證書 |
caPrivateKey | 細繩 | 選修的 | caCert 的私鑰 |
對於自簽名證書,必須設定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.ExtKeyUsageCodeSigning |
EmailProtection | x509.ExtKeyUsageEmailProtection |
IPSecEndSystem | x509.ExtKeyUsageIPSECEndSystem |
IPSecTunnel | x509.ExtKeyUsageIPECTunnel |
IPSecUser | x509.ExtKeyUsageIPSECUser |
TimeStamping | x509.ExtKeyUsageTimeStamping |
OCSPSigning | x509.ExtKeyUsageOCSPSigning |
MicrosoftServerGatedCrypto | x509.ExtKeyUsageMicrosoftServerGatedCrypto |
NetscapeServerGatedCrypto | x509.ExtKeyUsageNetscapeServerGatedCrypto |
MicrosoftCommercialCodeSigning | x509.ExtKeyUsageMicrosoftCommercialCodeSigning |
MicrosoftKernelCodeSigning | x509.ExtKeyUsageMicrosoftKernelCodeSigning |
例如:
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 | 細繩 | 總是 | 開始時間,格式為“Jan 1 01:22:31 2019” |
validUntil | 細繩 | 總是 | 開始時間,格式為“Jan 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 支援一些有用的功能來與wireguard金鑰配合使用。另請參閱“有用的知識”部分,以了解一些提供狀態的提示。
(( wggenkey() ))
此功能可用於產生私有wireguard金鑰。結果將進行 Base64 編碼。
例如:
keys :
key : (( wggenkey() ))
解析為類似的東西
key : WH9xNVJuSuh7sDVIyUAlmxc+woFDJg4QA6tGUVBtGns=
(( wgpublickey(key) ))
對於給定的金鑰(例如使用 wggenkey 函數產生的金鑰),此函數會提取公鑰並以 Base64 格式再次傳回它 -
例如:
keys :
key : (( wggenkey() ))
public : (( wgpublickey(key)
解析為類似的東西
key : WH9xNVJuSuh7sDVIyUAlmxc+woFDJg4QA6tGUVBtGns=
public : n405KfwLpfByhU9pOu0A/ENwp0njcEmmQQJvfYHHQ2M=
(( lambda |x|->x ":" port ))
Lambda 表達式可用來定義其他匿名函數。它們可以作為值分配給 yaml 節點,並透過路徑表達式引用,以在其他動態表達式中使用適當的參數呼叫函數。對於最終文檔,它們被映射到字串值。
Lambda表達式有兩種形式。儘管
lvalue : (( lambda |x|->x ":" port ))
透過直接從動態表達式中獲取元素來產生一個函數,
string : " |x|->x " : " port "
lvalue : (( lambda string ))
評估表達式的函數結果。表達式必須評估為函數或字串。如果將表達式評估為字串,則將從字串中解析函數。
由於lambda表達式的評估結果是常規值,因此它也可以作為參數傳遞以函數呼叫並沿存根處理沿值合併為值。
一個完整的範例可能如下所示:
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
如果完整的表達式是lambda表達式,則可以省略關鍵字lambda
。
lambda表達式評估了lambda值,這些值用作SPIFF處理的YAML文件中的最終值。
注意:如果最終文檔仍然包含lambda值,則將它們轉移到文字表示。如果文件已透過SPIFF重新處理,則不能保證可以再次正確解析此表示形式。特別是對於複雜的範圍和咖哩功能,這是不可能的。
因此,功能節點應始終是臨時的或本地的,以便在處理或合併期間可用,但省略了最終文件。
典型的函數呼叫使用位置參數。在這裡,給定參數滿足給定順序中聲明的函數參數。對於lambda值,也可以在呼叫表達式中使用命名的參數。在這裡,一個參數被指派給lambda表達式宣告的專用參數。可以任意選擇指定參數的順序。
例如:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=1, b=2, a=1) ))
也可以合併命名的位置論點。因此,位置參數必須遵循指定的論點。
例如:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=1, 1, 2) ))
同一論點不得被兩個命名和位置論點所滿足。
而不是使用參數名稱,而是可以使用參數索引。
例如:
func : (( |a,b,c|->{$a=a, $b=b, $c=c } ))
result : (( .func(3=1, 1) ))
因此,此功能似乎是無用的,但是如下一個段落所示,它會與可選參數或咖哩相結合,顯示出它的力量。
lambda表達式可能是指該呼叫實際YAML文件的絕對或相對節點。相對引用是在函數呼叫的上下文中評估的。所以
lvalue : (( lambda |x,y|->x + y + offset ))
offset : 0
values :
offset : 3
value : (( .lvalue(1,2) ))
值為6
values.value
。
除指定的參數外,還有一個隱式名稱( _
),可用來引用函數本身。它可用於定義自遞歸功能。連同邏輯和條件運算子可以定義fibunacci函數:
fibonacci : (( lambda |x|-> x <= 0 ? 0 :x == 1 ? 1 :_(x - 2) + _( x - 1 ) ))
value : (( .fibonacci(5) ))
產生值8
的value
屬性。
預設情況下,在lambda deptinition的靜態範圍中評估了lambda表達式中的參考表達式,然後是呼叫者的靜態YAML範圍。絕對參考始終在呼叫者的文件範圍中評估。
此名稱_
也可以用作錨定,以指稱定義lambda函數的YAML文件中Lambda表達式的靜態定義範圍。這些引用始終被解釋為與此靜態YAML文件範圍相關的相對引用。存取此定義範圍的根元素沒有含義。
相對名稱可用於存取Dynaml表達式中給出的靜態定義範圍(外部範圍文字和外部lambda參數的參數)
例如:
env :
func : (( |x|->[ x, scope, _, _.scope ] ))
scope : definition
call :
result : (( env.func("arg") ))
scope : call
產生result
列表:
call :
result :
- arg
- call
- (( lambda|x|->[x, scope, _, _.scope] )) # the lambda expression as lambda value
- definition
這也跨多個存根運作。定義上下文是lambda表達式定義的存根,即使它在鏈條下的存根中使用。因此,可以在lambda表達式中使用引用,在呼叫者位置看不到,他們將其定義的靜態YAML文件範圍帶有其定義範圍。
內部lambda表達式記住外部lambda表達式的局部結合。這可以用於基於外部函數的參數返回函數。
例如:
mult : (( lambda |x|-> lambda |y|-> x * y ))
mult2 : (( .mult(2) ))
value : (( .mult2(3) ))
產量為6
財產value
。
透過在宣告中指派值,可以在lambda表達式中預設尾隨參數。然後,這些參數是可選的,不需要為函數呼叫中的這些參數指定參數。
例如:
mult : (( lambda |x,y=2|-> x * y ))
value : (( .mult(3) ))
產量為6
財產value
。
可能預設lambda表達式的所有參數。然後可以在沒有參數的情況下呼叫該函數。預設參數可能沒有非預設參數。
帶有位置參數的呼叫只能省略從右到左的可選參數的參數。如果應該對正確的大多數參數有明確的參數,則必須使用所有參數的參數,必須使用所有參數。在這裡,可以在常規位置參數之前明確設定所需的可選參數。
例如:
func : (( |a,b=1,c=2|->{$a=a, $b=b, $c=c } ))
result : (( .func(c=3, 2) ))
評估result
result :
a : 2
b : 1
c : 3
預設值的表達式不需要是恆定值甚至表達式,它可能是指YAML文件中的其他節點。在評估lambda表達式時,始終在Lambda表達式聲明的範圍內評估預設表達式。
例如:
Stub.yaml
default : 2
mult : (( lambda |x,y=default * 2|-> x * y ))
template.yaml
mult : (( merge ))
scope :
default : 3
value : (( .mult(3) ))
評估value
為12
lambda表達式參數清單中的最後一個參數可能是varargs參數,該參數在fnction呼叫中消耗其他參數。此參數始終是值列表,每個參數一個條目。
varargs參數以a ...
表示最後一個參數名稱表示。
例如:
func : (( |a,b...|-> [a] b ))
result : (( .func(1,2,3) ))
產生列表[1, 2, 3]
的屬性result
。
如果沒有給予varargs參數的參數,則其值是空列表。
...
運算符也可以用於內聯列表擴充。
如果應由命名參數設定vararg參數,其值必須是列表。
使用咖哩運算子( *(
),可以透過指定領先的參數值來將lambda函數轉換為另一個函數。
結果是一個新函數,以丟失的參數(咖哩)和使用原始功能主體具有指定參數的靜態綁定。
例如:
mult : (( lambda |x,y|-> x * y ))
mult2 : (( .mult*(2) ))
value : (( .mult2(3) ))
咖哩可以與預設參數結合使用。但是所得的函數並不預設主參數,它只是一個新函數,具有更少的參數固定指定的函數。
如果原始函數使用變數參數列表,則咖哩可能跨越任何數量的變數參數部分,但是一旦給出了至少一個這樣的參數,則可以滿足變數部分的參數。咖哩功能的函數呼叫無法擴充。
例如:
func : (( |a,b...|->join(a,b) ))
func1 : (( .func*(",","a","b")))
# invalid: (( .func1("c") ))
value : (( .func1() ))
評估value
為"a,b"
。
也可以將咖哩用於內建功能,例如join
。
例如:
stringlist : (( join*(",") ))
value : (( .stringlist("a", "b") ))
評估value
為"a,b"
。
像defined
一樣,有幾種內建功能作用於未評估或不可評估的論點。因為這些功能是不可能的。
只有從右到左才能使用位置參數咖哩。但是,也可以為命名參數完成咖哩。在這裡,無論參數清單中的位置如何,任何參數組合都可以預設。然後,結果功能以其原始順序具有不滿意的參數。切換參數順序是不可能的。
例如:
func : (( |a,b=1,c=2|->{$a=a, $b=b, $c=c } ))
curry : (( .func(c=3, 2) ))
result : (( .curry(5) ))
評估result
result :
a : 2
b : 5
c : 3
結果函數保持參數b
。因此,將保留預設值。因此,它只能在沒有參數( .curry()
)的情況下稱呼它
result :
a : 2
b : 1
c : 3
注意力:
出於相容的原因,如果沒有預設參數的lambda函數的參數少於聲明的參數,則也完成了咖哩的原因。
這種行為被棄用,將來將被刪除。它被咖哩操作員取代。
例如:
mult : (( lambda |x,y|-> x * y ))
mult2 : (( .mult(2) ))
value : (( .mult2(3) ))
評估value
為6。
(( catch[expr|v,e|->v] ))
此表達式評估表達式( expr
),然後執行具有表達式評估狀態的lambda函數。即使表達失敗,它也總是成功。 lambda函數可能需要一個或兩個參數,第一個函數總是評估的值(或nil
的情況)。可選的第二個參數取得錯誤訊息,表達式的評估失敗(或否則nil
)
此函數的結果是整個表達式的結果。如果功能失敗,則完整的表達式失敗。
例如:
data :
fail : (( catch[1 / 0|v,e|->{$value=v, $error=e}] ))
valid : (( catch[5 * 5|v,e|->{$value=v, $error=e}] ))
決心
data :
fail :
error : division by zero
value : null
valid :
error : null
value : 25
(( sync[expr|v,e|->defined(v.field),v.field|10] ))
如果表達式expr
可能會傳回不同的評估結果,則可以將最終輸出與表達式值的專用條件同步。例如,這樣的表達可能是未經read
, exec
或pipe
呼叫。
第二個要素必須評估為lambda值,如標題所示,由正規表示式或lambda文字賦予。它可能需要一個或兩個參數,即值表達式的實際值,並且在評估失敗的情況下,可選地是錯誤訊息。評估LAMDA表現的結果決定了值表達的評估狀態是否可以接受( true
)(true)( false
)。
如果接受該值,則使用可選的第三個表達式來確定sync[]
表達式的最終結果。它可以作為評估lambda值的表達方式,也可以透過使用與先前的lambda文字相同的結合進行逗號分隔。如果沒有給出,則傳回同步表達式的值。
如果該值不可接受,則在應用逾時之前重複評估。秒內的超時由可選的第四個表達式(預設為5分鐘)給出。可以省略第四個或兩個元素。
可以將lambda值作為字面形式,也可以透過表達方式給出,從而導致以下風味:
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]
有或沒有超時表達式。
例如:
data :
alice : 25
result : (( sync[data|v|->defined(v.alice),v.alice] ))
決心
data :
alice : 25
result : 25
這個範例是無用的,因為同步表達式是常數。它只是證明了用法。
映射用於從列表或映射的條目中產生新列表,或來自包含Dynaml表達式處理條目的地圖條目的新地圖。此表達式由lambda函數給出。映射函數有兩種基本形式:它可以像(( map[list|x|->x ":" port] ))
,也可以通過評估lambda函數的常規動態表達式來確定它如(( map[list|mapping.expression))
(此處映射取自屬性mapping.expression
,該映射應具有適當的lambda函數)。
映射有兩個目標風味:在語法中使用[]
或{}
。第一種味道總是從給定來源的條目中產生列表。第二個僅採用地圖來源並產生過濾或轉換的地圖。
此外,映射使用三種基本的映射行為:
map
轉換值。在這裡,lambda函數的結果用作替換原始值的新值。或者select
過濾。在此,lambda函數的結果用作布林值,以決定是否應保留( true
)或省略( false
)。sum
組成。這裡始終使用列表的味道,但是結果類型和內容是透過將一個條目依次匯總為一個任意初始值來完全決定了語句的參數化。注意:作為映射語法的一部分,未設定特殊參考_
lambda函數。因此,可以在常規的lambda函數中使用映射語句(以及使用lambda函數作為其語法的一部分的所有其他語句),而無需阻礙周圍的顯式lambda表達式的這種特殊折磨的含義。
(( map[list|elem|->dynaml-expr] ))
在清單成員上執行映射表達式,以產生新的(映射)清單。第一個表達式( list
)必須解析為列表。最後一個表達式( x ":" port
)定義用於映射給定列表的所有成員的映射表達式。在此表達式中,任意聲明的簡單參考名稱(此處x
)可用於存取實際處理的清單元素。
例如
port : 4711
hosts :
- alice
- bob
mapped : (( map[hosts|x|->x ":" port] ))
產量
port : 4711
hosts :
- alice
- bob
mapped :
- alice:4711
- bob:4711
例如,可以將此表達與其他人結合在一起:
port : 4711
list :
- alice
- bob
joined : (( join( ", ", map[list|x|->x ":" port] ) ))
神奇地提供了逗號分開的移植主機清單:
port : 4711
list :
- alice
- bob
joined : alice:4711, bob:4711
(( map[list|idx,elem|->dynaml-expr] ))
在此變體中,第一個參數idx
帶有索引和第二個elem
帶有索引值。
例如
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] ))
使用映射表達式將地圖映射到列表。該表達式可以存取鍵和/或值。如果聲明兩個引用,則兩個值都傳遞給表達式,第一個值將帶有鍵,第二個值帶有鍵的值。如果聲明一個參考,則僅提供值。
例如
ages :
alice : 25
bob : 24
keys : (( map[ages|k,v|->k] ))
產量
ages :
alice : 25
bob : 24
keys :
- alice
- bob
(( map{map|elem|->dynaml-expr} ))
在映射語法中使用{}
而不是[]
,結果再次是舊鍵和新輸入值的地圖。至於列表映射,可以在變數列表中指定一個密鑰變數。
persons :
alice : 27
bob : 26
older : (( map{persons|x|->x + 1} ) ))
只需在older
欄位中遞增所有條目的值:
older :
alice : 28
bob : 27
評論
另一種表達同樣的方法是使用sum[persons|{}|s,k,v|->s { k = v + 1 }]
。
(( map{list|elem|->dynaml-expr} ))
將{}
而不是[]
與映射語法中的列表一起使用,結果再次是列表元素作為鍵和映射的輸入值的地圖。對於此列表,必須是字串。至於列表映射,可以在變數列表中指定索引變數。
persons :
- alice
- bob
length : (( map{persons|x|->length(x)} ) ))
只需建立一個地圖將清單條目映射到其長度:
length :
alice : 5
bob : 3
(( select[expr|elem|->dynaml-expr] ))
使用select
地圖或列表,可以透過評估每個條目的布林表達式來過濾。如果表達式評估為真等值,則選擇條目。 (請參閱條件)。
基本上,它提供了所有可用於map[]
例如
list :
- name : alice
age : 25
- name : bob
age : 26
selected : (( select[list|v|->v.age > 25 ] ))
評估選定的
selected :
- name : bob
age : 26
評論
另一種表達同樣的方法是使用map[list|v|->v.age > 25 ? v :~]
。
(( select{map|elem|->dynaml-expr} ))
在映射語法中使用{}
而不是[]
,結果再次是一個映射,其映射是由給定表達式過濾的舊鍵。
persons :
alice : 25
bob : 26
older : (( select{persons|x|->x > 25} ))
只需將所有條目都以大於25的價值保留,並省略了所有條目:
selected :
bob : 26
這種味道僅適用於地圖。
評論
另一種表達同樣的方法是使用sum[persons|{}|s,k,v|->v > 25 ? s {k = v} :s]
。
聚合用於從清單的條目或透過動態表達式匯總條目的單一結果。此表達式由lambda函數給出。聚合函數有兩種基本形式:它可以像(( sum[list|0|s,x|->s + x] ))
中列為lambda函數如(( sum[list|0|aggregation.expression))
(此處取自屬性aggregation.expression
,該函數應具有適當的lambda函數)。
(( sum[list|initial|sum,elem|->dynaml-expr] ))
在清單成員上執行聚合表達式以產生聚合結果。第一個表達式( list
)必須解析為列表。第二個表達式用作聚集的初始值。最後一個表達式( s + x
)定義用於聚集給定列表的所有成員的聚合表達式。在此表達式內部,任意聲明的簡單參考名稱(此處s
)可用於存取中間聚合結果,第二個參考名稱(此處x
)可用於存取實際處理的清單元素。
例如
list :
- 1
- 2
sum : (( sum[list|0|s,x|->s + x] ))
產量
list :
- 1
- 2
sum : 3
(( sum[list|initial|sum,idx,elem|->dynaml-expr] ))
在此變體中,第二個參數idx
由索引和第三個elem
提供了索引值。
例如
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] ))
使用聚合表達式將MAP元素的聚合到單一結果。該表達式可以存取鍵和/或值。第一個參數始終是中間聚合結果。如果聲明了三個引用,則兩個值都傳遞給了表達式,第二個值將帶有鍵,第三個值帶有鍵的值。如果聲明了兩個引用,則僅提供第二個參考文獻。
例如
ages :
alice : 25
bob : 24
sum : (( map[ages|0|s,k,v|->s + v] ))
產量
ages :
alice : 25
bob : 24
sum : 49
預測在清單或地圖的元素上工作,得出結果清單。因此,每個元素都由可選的後續參考表達式映射。這可能再次包含預測,動態引用或lambda呼叫。基本上,這是更通用映射的簡化形式,該映射產生了與lambda函數一起使用的列表,僅使用基於元素的參考表達式。
(( expr.[*].value ))
隨後的參考表達式(此處.expr
)將表達式expr
給出的映射或列表的所有元素均列出。如果此表達式在地圖上起作用,則將元素訂購為其鑰匙值。如果省略了後續參考表達式,則將完整的值清單iStrun。對於列表表達式,這意味著身份操作。
例如:
list :
- name : alice
age : 25
- name : bob
age : 26
- name : peter
age : 24
names : (( list.[*].name ))
names
的收益:
names :
- alice
- bob
- peter
或用於地圖:
networks :
ext :
cidr : 10.8.0.0/16
zone1 :
cidr : 10.9.0.0/16
cidrs : (( .networks.[*].cidr ))
cidrs
的產量:
cidrs :
- 10.8.0.0/16
- 10.9.0.0/16
(( list.[1..2].value ))
這種投影味僅適用於清單。此投影是針對初始清單的專用切片完成的。
例如:
list :
- name : alice
age : 25
- name : bob
age : 26
- name : peter
age : 24
names : (( list.[1..2].name ))
names
的收益:
names :
- bob
- peter
在參數列表或列表文字中,可以使用列表擴充運算子( ...
)。它是任何清單表達式上的後綴操作員。它用列表成員的序列代替列表表達式。它可以與靜態列表參數表示結合使用。
例如:
list :
- a
- b
result : (( [ 1, list..., 2, list... ] ))
評估result
result :
- 1
- a
- b
- 2
- a
- b
以下範例證明了與varargs運算子在函數中結合使用的用法:
func : (( |a,b...|-> [a] b ))
list :
- a
- b
a : (( .func(1,2,3) ))
b : (( .func("x",list..., "z") ))
c : (( [ "x", .func(list...)..., "z" ] ))
評估以下結果:
a :
- 1
- 2
- 3
b :
- x
- a
- b
- z
c :
- x
- a
- b
- z
請注意,列表擴展可能涵蓋Lambda函數呼叫中的多個參數(包括varargs參數)。
可以標記YAML文件的節點以啟用該節點的專用行為。這樣的標記是動態語法的一部分,可以將其加入任何動態表達式中。他們用直接的&
表示標記名稱。如果表達式是標記和正規表示式的組合,則該表達式遵循括號中包含的標記清單(例如(( &temporary( a + b ) ))
)。
注意:而不是使用<<:
插入欄位放置標記,而是現在可以使用<<<:
也可以使用常規的yaml parsers作為Spiffillike yaml文件。 <<:
保留是為了向後相容。
(( &temporary ))
地圖,列表或簡單值節點可以標記為臨時性。從最終輸出文件中刪除臨時節點,但在合併和動態評估期間可用。
例如:
temp :
<< : (( &temporary ))
foo : bar
value : (( temp.foo ))
產量:
value : bar
新增- <<: (( &temporary ))
到清單中可以用來將清單標記為臨時。
臨時標記可以與常規的動態表達式結合使用,以標記平原場。因此,括號的表達只是附加到標記
例如:
data :
alice : (( &temporary ( "bar" ) ))
foo : (( alice ))
產量:
data :
foo : bar
臨時標記可以與模板標記結合起來,從最終輸出省略模板。
(( &local ))
在解析動態表達式後,標記&local
行為類似於&temporary
但局部節點總是會直接從存根中刪除。因此,此類節點不能合併,也不用於進一步合併存根,最後是模板。
(( &dynamic ))
此標記可用於標記範本表達式(直接或引用),以在使用節點覆蓋或註入處理鏈沿節點值時,在用法上下文中強制對模板的重新評估。它也可以與&inject
或&default
一起使用。
例如:
template.yaml
data : 1
合併於
Stub.yaml
id : (( &dynamic &inject &template(__ctx.FILE) ))
將下定決心
id : template.yaml
data : 1
原始模板沿著合併鏈保存,並在使用的簡短或模板的上下文中分別評估。
將此標記用於節點,而不是對範本值進行評估。
(( &inject ))
此標記要求將標記的項目注入下一個存根級別,即使是託管元素(清單或地圖)也不要求合併。只有當下一個級存根已經包含託管元素時,這才有效。
例如:
template.yaml
alice :
foo : 1
Stub.yaml
alice :
bar : (( &inject(2) ))
nope : not injected
bob :
<< : (( &inject ))
foobar : yep
被合併到
alice :
foo : 1
bar : 2
bob :
foobar : yep
(( &default ))
標記為預設值的節點將用作下游存根層級的預設值。如果未設定此類條目,它將表現為&inject
,並隱式地添加此節點,但現有設定不會被覆蓋。
標記為預設值的地圖(或清單)將被視為值。如果未在下游定義此類字段,則將圖表作為預設情況使用。
例如:
template.yaml
data : { }
Stub.yaml
data :
foobar :
<< : (( &default ))
foo : claude
bar : peter
被合併到
data :
foobar :
foo : claude
bar : peter
他們的條目既不用於覆蓋現有的下游值,也不用於預設未預設地圖欄位的非外觀欄位。
例如:
template.yaml
data :
foobar :
bar : bob
Stub.yaml
data :
foobar :
<< : (( &default ))
foo : claude
bar : peter
被合併到
data :
foobar :
bar : bob
如果需要子續默認,則必須再次將預設地圖的欄位標記為預設值。
例如:
template.yaml
data :
foobar :
bar : bob
Stub.yaml
data :
foobar :
<< : (( &default ))
foo : (( &default ("claude") ))
bar : peter
被合併到
data :
foobar :
foo : claude
bar : bob
注意:標記為預設值的清單條目的行為不確定。
(( &state ))
在合併處理過程中處理標記為狀態的節點,就好像不存在標記一樣。但是,在模板處理結束時,將有一個特殊的處理(選項--state <path>
)。除常規輸出外,僅由狀態節點(加上所有巢狀節點)組成的文件將寫入狀態文件。該文件將用作啟用狀態支援的進一步合併處理的頂級存根。
這使得可以在兩個合併過程之間保持狀態。對於常規合併,僅在第一個處理過程中處理SICH節點。以後的處理將使狀態遠離第一個狀態,因為這些節點將被添加到子列表末尾的狀態存根越過。
如果這些節點也其他停用合併(例如(例如(( &state(merge none) ))
)子層級節點中的Dynaml表達式可以使用函數stub()
執行明確合併,以參考已處理過的存根(尤其是隱式新增)狀態存根)。有關範例,請參考州庫。
(( &template ))
標記為模板的節點將不會在其發生的位置進行評估。相反,它們將導致儲存為節點值的模板值。後來可以在動態表達式內實例化(見下文)。
(( &tag:name ))
標記標記可用於將邏輯名稱指派給節點值。然後可以在標記的參考表達式中使用此名稱來參考此節點值(見下文)。
標記的參考具有<tagname>::<path>
。 <path>
路徑>可以表示標記節點的任何子節點。如果應使用完整節點的值(或簡單值節點),則<path>
必須表示root路徑( .
)。
標籤可用於將節點值標記在多重文件流中(用作範本)。在為文件定義後,可以將標籤用於從多文件流中的文件序列的實際或上一個文件中引用節點值。可以為複雜或簡單的值節點新增標籤。標記的參考可以用來指標記值作為整體或子結構。
(( &tag:name(value) ))
此語法用於標記一個由動態表達式定義的值的節點。它也可以用來表示標記的簡單值節點。 (與往常一樣,值零件是可選的,用於將標記添加到結構化值(請參閱標記)。
例如:
template.yaml
data :
<< : (( &tag:persons ))
alice : (( &tag:alice(25)
如果該名稱以恆星( *
)為前綴,則標籤在全球定義。 gobal標籤Surive存根處理及其價值在隨後的存根(和模板)處理中可見。
標籤名稱可能由多個由結腸分離的組件組成:
:)。
也可以透過Dynaml函數TagDef動態定義標籤。
(( tag::foo ))
引用標記節點值的子路徑。
例如:
template.yaml
data :
<< : (( &tag:persons ))
alice : 25
tagref : (( persons::alice ))
將tagref
解析為25
(( tag::. ))
引用標記節點的整體(結構化)值。
例如:
template.yaml
data :
alice : (( &tag:alice(25) ))
tagref : (( alice::. ))
將tagref
解析為25
(( foo.bar::alice ))
標籤名稱可能是結構化的。標籤名稱由一個非空的標籤組件清單組成,該標籤組件被點或colon :
:)隔開。標籤組件可能包含ASCII字母或數字,開始字母。多組分標籤需要標籤解析度。
標籤引用總是包含一個標籤名稱和一個由雙重結腸( ::
:)隔開的路徑。標準用例是描述標記節點值的專用子節點。
例如,如果標籤X
描述了值
data :
alice : 25
bob : 24
標記的參考X::data.alice
描述了值25
。
對於帶有路徑以外的路徑的標記引用.
(整個標籤值)結構化標籤具有更複雜的解析度機制。結構化標籤由多個標籤組件組成,該標籤組件由結腸:
:)分開,例如lib:mylib
。因此,標籤跨越用於解析路徑參考的名稱空間或範圍的樹。無標籤的引用僅使用實際文件或綁定來解決路徑表達式。
評估標籤的路徑參考試圖在可用路徑的第一個標籤樹層級中解析路徑(廣度優先搜尋)。如果此層級包含可以解析給定路徑的多個標籤,則該解析度會失敗,因為它不能無意地解決。
例如:
tags :
- << : (( &tag:lib:alice ))
data : alice.alice
- << : (( &tag:lib:alice:v1))
data : alice.v1
- << : (( &tag:lib:bob))
other : bob
usage :
data : (( lib::data ))
有效地解決usage.data
到lib:alice::data
,從而將其與alice.alice
價值相關。
為了實現這一目標,所有匹配的子標籤都是由它們的標籤組件數量訂購的。選擇了包含這種給定路徑的第一個子級標籤。對於此級別,匹配標籤必須不含糊。該層級必須只有一個標籤,其中包含匹配路徑。如果有多重評估失敗。在上面的範例中,如果標籤lib:bob
將包含一個欄data
而不是other
其他欄位數據,則情況將會如此。
此功能可以在庫存根中使用,以提供其元素的合格名稱,這些名稱可用於將包含文件節點合併到範本中。
如果範本檔案是多檔案流,則在完整處理過程中保留了標籤。這意味著也可以在以下所有文件中使用標籤。但是,標籤名稱在多文件流中的所有文件中都必須是唯一的。
例如:
template.yaml
<< : (( &temporary ))
data :
<< : (( &tag:persons ))
alice : 25
bob : 24
---
alice : (( persons::alice ))
---
bob : (( persons::bob ))
決心
---
alice : 25
---
bob : 24
標記定義的標記可用於存根和範本。全域標籤可將存根處理可用於範本。本地標籤僅在聲明的處理層級上是avaialble。
除了標記明確設定的標籤外,在處理(多文件)範本的處理過程中,文件索引給出了隱式文件標籤。隱式文檔標籤具有前綴doc.
。此前綴不應用於文件中的標籤
例如:
template.yaml
<< : (( &temporary ))
data :
<< : (( &tag:persons ))
alice : 25
bob : 24
---
alice : (( persons::alice ))
prev : (( doc.1::. ))
---
bob : (( persons::bob ))
prev : (( doc.2::. ))
決心
---
alice : 25
prev :
data :
alice : 25
bob : 24
---
bob : 24
prev :
alice : 25
prev :
data :
alice : 25
bob : 24
如果給定的文檔索引為負,則表示相對於實際處理的文檔表示該文檔(因此,Tag doc.-1
表示上一個文檔)。索引doc.0
可用來表示實際文件。這裡總是必須指定路徑,不可能參考完整的文檔(帶有.
)。
可以用動態表達式標記地圖以用作模板。範本中的動態表達式未在文件中的定義位置進行評估,但可以使用Dynaml插入其他位置。在每個用法位置,都會單獨評估。
<<: (( &template ))
動態表達式&template
可用於將地圖節點標記為模板:
例如:
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
此範本將是節點foo.bar
的值。因此,可以在合併過程中的存根中透過存根來覆蓋它。未評估模板中的動態表達式。地圖只能有一個<<
字段。因此,僅透過在括號中添加表達式將模板標記與表達式結合在一起。
新增- <<: (( &template ))
到清單中,也可以定義清單範本。也可以透過將模板標記新增至表達式中,例如foo: (( &template (expression) ))
將單一表達式值轉換為簡單的模板。
模板標記可以與臨時標記結合起來,從最終輸出省略模板。
注意:而不是使用<<:
插入欄位放置範本標記,而是可以使用<<<:
<<:
保留是為了向後相容。
(( *foo.bar ))
動態表達式*<reference expression>
可用於評估YAML文件中某個地方的範本。模板中的動態表達式在此表達式的上下文中評估。
例如:
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
use :
subst : (( *foo.bar ))
verb : loves
verb : hates
評估
foo :
bar :
<< : (( &template ))
alice : alice
bob : (( verb " " alice ))
use :
subst :
alice : alice
bob : loves alice
verb : loves
verb : hates
_
特殊參考_
( self )可以在lambda函數和模板內使用。他們指的是包含元素(lambda函數或模板)。
此外,它可用於尋找相對參考表達式,從元素跳過中間範圍的定義文件範圍開始。
例如:
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) ))
評估call
call :
a : call
b : funcs
c : data
scope : call
__
特殊參考__
可用於尋找引用作為相對引用,從文件節點託管實際評估的動態表達式跳過中間範圍。
例如,除了地圖中的實際欄位外,可以使用這可以用來存取lambda值欄位。普通函數名稱的使用用於內建函數,不被用作相對引用。
此特殊參考也可以在範本中的表達式中找到,並參考託管實際評估表達式的範本中的地圖節點。
例如:
templates :
templ :
<< : (( &template ))
self : (( _ ))
value : (( ($self="value") __.self ))
result : (( scope ))
templ : (( _.scope ))
scope : templates
result :
inst : (( *templates.templ ))
scope : result
評估result
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
或引用上部節點:
templates :
templ :
<< : (( &template ))
alice : root
data :
foo : (( ($bob="local") __.bob ))
bar : (( ($alice="local") __.alice ))
bob : static
result : (( *templates.templ ))
評估result
result :
alice : root
data :
bar : root
foo : static
bob : static
___
特殊參考___
可用於在最外部範圍中找到引用。因此,它可用於存取用於透過命令列或API處理文件處理的處理綁定。如果未指定綁定,則使用文檔根。
呼叫spiff merge template.yaml --bindings bindings.yaml
bindings.yaml
input1 : binding1
input2 : binding2
和模板
template.yaml
input1 : top1
map :
input : map
input1 : map1
results :
frommap : (( input1 ))
fromroot : (( .input1 ))
frombinding1 : (( ___.input1 ))
frombinding2 : (( input2 ))
評估map.results
results :
frombinding1 : binding1
frombinding2 : binding2
frommap : map1
fromroot : top1
__ctx.OUTER
上下文欄位OUTER
用於嵌套合併。它是文檔列表,索引0是下一個外部文檔,依此類推。
(( {} ))
提供一個空的地圖。
(( [] ))
提供一個空列表。基本上,這不是一個專用的文字,而只是沒有價值的常規清單表達式。
(( ~ ))
提供空值。
(( ~~ ))
此字面意思評估不確定的表達。攜帶此值的元素(清單輸入或地圖欄位)雖然定義,但將從文件中刪除,並將其處理為未定義以進行進一步合併和評估參考表達式。
例如:
foo : (( ~~ ))
bob : (( foo || ~~ ))
alice : (( bob || "default"))
評估
alice : default
在每個動態表達式內部都有一個虛擬欄位__ctx
可用。它允許存取有關實際評估上下文的資訊。可以透過相對參考表達式存取。
支援以下欄位:
欄位名稱 | 類型 | 意義 |
---|---|---|
VERSION | 細繩 | 目前版本的Spiff |
FILE | 細繩 | 實際處理範本文件的名稱 |
DIR | 細繩 | 實際處理範本檔案的目錄名稱 |
RESOLVED_FILE | 細繩 | 帶有解決符號連結的實際處理範本檔案的名稱 |
RESOLVED_DIR | 細繩 | 帶有已解決符號連結的實際處理範本檔案目錄的名稱 |
PATHNAME | 細繩 | 實際處理欄位的路徑名 |
PATH | 列表[字串] | 路徑名作為組件列表 |
OUTER | yaml文檔 | 嵌套合併的外部文檔,索引0是下一個外部文檔 |
BINDINGS | yaml文檔 | 實際處理的外部綁定(另請參見___) |
如果指定了外部綁定,則它們是OUTER
的最後一個元素。
例如:
模板.yml
foo :
bar :
path : (( __ctx.PATH ))
str : (( __ctx.PATHNAME ))
file : (( __ctx.FILE ))
dir : (( __ctx.DIR ))
評估
例如:
foo :
bar :
dir : .
file : template.yml
path :
- foo
- bar
- path
str : foo.bar.str
評估動態表達式以遵守某些優先順序。這意味著首先評估具有較高優先順序的操作。例如,以1 + ( 2 * 3 )
順序評估表達式1 + 2 * 3
。從左到右評估具有相同優先順序的操作(與版本1.0.7相比)。這意味著表達式6 - 3 - 2
被評估為( 6 - 3 ) - 2
。
支援以下級別(從低優先級到高優先級)
||
, //
foo bar
)-or
-and
==
<=
!=
<
>
>=
+
, -
*
, /
, %
( )
!
,常數,參考( foo.bar
), merge
, auto
, lambda
, map[]
和功能完整的語法可以在Dynaml.peg中找到。
特徵狀態:alpha
注意:這是一個alpha功能。必須在命令列上啟用--interpolation
或--features=interpolation
選項。同樣,對於Spiff庫,必須明確啟用它。透過將密鑰interpolation
新增至儲存在環境變數SPIFF_FEATURES
中的功能清單中,預設將啟用此功能。
通常,完整的值可以是文字或動態表達式。對於字串文字,可以使用插值語法將動態表達式嵌入字串中。
例如
data : test
interpolation : this is a (( data ))
透過所描述的表達式評估的結果,替換了雙括號之間的零件。這裡可以透過通常的逃脫(( ((!
)語法),可以逃脫支架。
這些字串文字將隱式轉換為完整的平面動態表達式。 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
決心
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
決心
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
決心
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 ))
決心
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
決心
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
決心
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
部署.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
模板.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:
模板.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
模板.yml
foo : (( ["alice"] ))
和
stub.yml
foo :
- peter
- paul
產量
foo :
- peter
- paul
但模板
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
模板.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.在下面的例子中
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") ))
評估
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 ))
評估
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
模板.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
模板.yml
alice : 24
bob : 25
peter : 26
和
mapping.yml
config :
alice : (( ~~ ))
bob : (( ~~ ))
alice : (( config.alice || ~~ ))
bob : (( config.bob || ~~ ))
peter : (( config.peter || ~~ ))
和
配置.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 ))
評估
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
評估
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 ))
}
它支持